Refactoring Route, Speed

This commit is contained in:
Dimitris
2025-12-29 15:44:52 +01:00
parent 1b8abbd4eb
commit 82027dce76
32 changed files with 350 additions and 134 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 = 14 versionCode = 15
versionName = "0.1.3.14" versionName = "0.1.3.15"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -2,22 +2,34 @@ package com.kouros.navigation
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository 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 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.androidContext
import org.koin.android.ext.koin.androidLogger import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import org.koin.core.logger.Level import org.koin.core.logger.Level
import org.maplibre.compose.expressions.dsl.switch
class MainApplication : Application() { class MainApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ObjectBox.init(this); ObjectBox.init(this);
appContext = applicationContext appContext = applicationContext
setIntKeyValue(appContext!!, RouteEngine.VALHALLA.ordinal, ROUTE_ENGINE)
navigationViewModel = getRouteEngine(appContext!!)
startKoin { startKoin {
androidLogger(Level.DEBUG) androidLogger(Level.DEBUG)
androidContext(this@MainApplication) androidContext(this@MainApplication)
@@ -26,11 +38,12 @@ class MainApplication : Application() {
} }
companion object { companion object {
var appContext: Context? = null var appContext: Context? = null
private set private set
var useContacts = false var useContacts = false
val navigationViewModel = ViewModel(ValhallaRepository()) lateinit var navigationViewModel : ViewModel
} }
} }

View File

@@ -11,4 +11,5 @@ import org.koin.dsl.module
val appModule = module { val appModule = module {
viewModelOf(::ViewModel) viewModelOf(::ViewModel)
singleOf(::ValhallaRepository) singleOf(::ValhallaRepository)
singleOf(::OsrmRepository)
} }

View File

@@ -48,7 +48,9 @@ import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -77,7 +79,7 @@ class MainActivity : ComponentActivity() {
var lastLocation = location(0.0, 0.0) var lastLocation = location(0.0, 0.0)
val observer = Observer<String> { newRoute -> val observer = Observer<String> { newRoute ->
if (newRoute.isNotEmpty()) { if (newRoute.isNotEmpty()) {
routeModel.startNavigation(newRoute) routeModel.startNavigation(newRoute, applicationContext)
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.route.routeGeoJson
simulate() simulate()
//test() //test()
@@ -295,12 +297,15 @@ class MainActivity : ComponentActivity() {
} }
} }
@OptIn(DelicateCoroutinesApi::class) fun simulate() {
fun simulate() = GlobalScope.async { CoroutineScope(Dispatchers.IO).launch {
for ((index, step) in routeModel.legs.steps.withIndex()) { for ((index, step) in routeModel.legs.steps.withIndex()) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
mock.setMockLocation(waypoint[1], waypoint[0]) if (routeModel.isNavigating()) {
delay(600L) // mock.setMockLocation(waypoint[1], waypoint[0])
delay(800L) //
}
}
} }
} }
} }

View File

@@ -62,7 +62,13 @@ fun MapView(
Column { Column {
NavigationInfo(step) NavigationInfo(step)
Box(contentAlignment = Alignment.Center) { Box(contentAlignment = Alignment.Center) {
MapLibre(applicationContext, cameraState, baseStyle, route, ViewStyle.VIEW) MapLibre(
applicationContext,
cameraState,
baseStyle,
route,
ViewStyle.VIEW
)
LocationTrackingEffect( LocationTrackingEffect(
locationState = userLocationState, locationState = userLocationState,
) { ) {

View File

@@ -257,7 +257,7 @@ private fun RecentPlaces(
modifier = Modifier.size(24.dp, 24.dp), modifier = Modifier.size(24.dp, 24.dp),
) )
ListItem( ListItem(
headlineContent = { Text("${place.name} ${place.postalCode}") }, headlineContent = { Text("${place.street} ${place.postalCode} ${place.city}") },
modifier = Modifier modifier = Modifier
.clickable { .clickable {
val toLocation = location(place.longitude, place.latitude) val toLocation = location(place.longitude, place.latitude)

View File

@@ -24,11 +24,15 @@ import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen 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.ROUTE_ENGINE
import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
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.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
class NavigationSession : Session(), NavigationScreen.Listener { class NavigationSession : Session(), NavigationScreen.Listener {
@@ -69,7 +73,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
val navigationViewModel = ViewModel(ValhallaRepository()) lateinit var navigationViewModel : ViewModel
init { init {
val lifecycle: Lifecycle = lifecycle val lifecycle: Lifecycle = lifecycle
@@ -77,6 +81,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
override fun onCreateScreen(intent: Intent): Screen { override fun onCreateScreen(intent: Intent): Screen {
navigationViewModel = getRouteEngine(carContext)
routeModel = RouteCarModel() routeModel = RouteCarModel()
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)

View File

@@ -34,6 +34,7 @@ import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
@@ -57,7 +58,7 @@ class SurfaceRenderer(
private val cameraPosition = MutableLiveData( private val cameraPosition = MutableLiveData(
CameraPosition( CameraPosition(
zoom = 15.0, zoom = 15.0,
target = Position(latitude = 48.1857475, longitude = 11.5793627) target = Position(latitude = homeLocation.latitude, longitude = homeLocation.longitude)
) )
) )
private var visibleArea = MutableLiveData( private var visibleArea = MutableLiveData(
@@ -68,6 +69,7 @@ class SurfaceRenderer(
var height = 0 var height = 0
var lastBearing = 0.0 var lastBearing = 0.0
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val speedCamerasData = MutableLiveData("")
val speed = MutableLiveData(0F) val speed = MutableLiveData(0F)
lateinit var centerLocation: Location lateinit var centerLocation: Location
var viewStyle = ViewStyle.VIEW var viewStyle = ViewStyle.VIEW
@@ -165,11 +167,11 @@ class SurfaceRenderer(
} }
} }
@Composable @Composable
fun MapView() { fun MapView() {
val position: CameraPosition? by cameraPosition.observeAsState() val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState() val route: String? by routeData.observeAsState()
val speedCameras: String? by speedCamerasData.observeAsState()
val paddingValues = getPaddingValues(height, viewStyle) val paddingValues = getPaddingValues(height, viewStyle)
val cameraState = cameraState(paddingValues, position, tilt) val cameraState = cameraState(paddingValues, position, tilt)
@@ -177,7 +179,7 @@ class SurfaceRenderer(
mutableStateOf(BaseStyle.Uri(Constants.STYLE)) mutableStateOf(BaseStyle.Uri(Constants.STYLE))
} }
DarkMode(carContext, baseStyle) DarkMode(carContext, baseStyle)
MapLibre(carContext, cameraState, baseStyle, route, viewStyle) MapLibre(carContext, cameraState, baseStyle, route, viewStyle, speedCameras)
ShowPosition(cameraState, position, paddingValues) ShowPosition(cameraState, position, paddingValues)
} }
@@ -189,9 +191,15 @@ class SurfaceRenderer(
) { ) {
val cameraDuration = val cameraDuration =
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) {
DrawNavigationImages(paddingValues, currentSpeed, routeModel.routeState.maxSpeed, width, height) DrawNavigationImages(
paddingValues,
currentSpeed,
routeModel.routeState.maxSpeed,
width,
height
)
} }
LaunchedEffect(position, viewStyle) { LaunchedEffect(position, viewStyle) {
cameraState.animateTo( cameraState.animateTo(
@@ -237,7 +245,11 @@ class SurfaceRenderer(
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
synchronized(this) { synchronized(this) {
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) { if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
val bearing = bearing(lastLocation, location, cameraPosition.value!!.bearing) val bearing = bearing(
lastLocation,
location,
cameraPosition.value!!.bearing
)
val zoom = if (viewStyle == ViewStyle.VIEW) { val zoom = if (viewStyle == ViewStyle.VIEW) {
calculateZoom(location.speed.toDouble()) calculateZoom(location.speed.toDouble())
} else { } else {

View File

@@ -47,6 +47,7 @@ import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.FeatureCollection import org.maplibre.spatialk.geojson.FeatureCollection
import org.maplibre.spatialk.geojson.Point import org.maplibre.spatialk.geojson.Point
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.absoluteValue
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.sqrt import kotlin.math.sqrt
@@ -244,8 +245,8 @@ private fun rememberLocationSource(locationState: Location): GeoJsonSource {
buildJsonObject { buildJsonObject {
put("accuracy", location.accuracy) put("accuracy", location.accuracy)
put("bearing", location.bearing) put("bearing", location.bearing)
//put("bearingAccuracy", location.bearingAccuracy) put("bearingAccuracy", location.hasBearingAccuracy())
//put("age", location.timestamp.elapsedNow().inWholeNanoseconds) put("age", location.time.absoluteValue)
}, },
) )
) )

View File

@@ -1,6 +1,5 @@
package com.kouros.navigation.car.map package com.kouros.navigation.car.map
import android.annotation.SuppressLint
import android.location.Location import android.location.Location
import android.content.Context import android.content.Context
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
@@ -36,6 +35,7 @@ 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 com.kouros.navigation.utils.location
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
@@ -89,7 +89,8 @@ fun MapLibre(
cameraState: CameraState, cameraState: CameraState,
baseStyle: MutableState<BaseStyle.Uri>, baseStyle: MutableState<BaseStyle.Uri>,
route: String?, route: String?,
viewStyle: ViewStyle viewStyle: ViewStyle,
speedCameras: String? = ""
) { ) {
MaplibreMap( MaplibreMap(
options = MapOptions( options = MapOptions(
@@ -108,11 +109,12 @@ fun MapLibre(
} else { } else {
RouteLayer(route) RouteLayer(route)
} }
SpeedCameraLayer(speedCameras)
} }
//val lastLocation = location(cameraState.position.target.longitude, cameraState.position.target.latitude)
//Puck(cameraState, lastLocation) //Puck(cameraState, lastLocation)
} }
} }
@Composable @Composable
fun RouteLayer(routeData: String?) { fun RouteLayer(routeData: String?) {
if (routeData != null && routeData.isNotEmpty()) { if (routeData != null && routeData.isNotEmpty()) {
@@ -167,6 +169,28 @@ fun AmenityLayer(routeData: String?) {
} }
} }
@Composable
fun SpeedCameraLayer(speedCameras: String?) {
if (speedCameras != null && speedCameras.isNotEmpty()) {
val color = const(Color.DarkGray)
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
SymbolLayer(
id = "speed-camera-layer",
source = cameraSource,
iconImage = image(painterResource(R.drawable.speed_camera_48px), drawAsSdf = true),
iconColor = color,
iconSize =
interpolate(
type = exponential(1.2f),
input = zoom(),
5 to const(0.4f),
6 to const(0.7f),
7 to const(1.75f),
20 to const(3f),
),
)
}
}
@Composable @Composable
fun BuildingLayer(tiles: Source) { fun BuildingLayer(tiles: Source) {
Anchor.Replace("building-3d") { Anchor.Replace("building-3d") {
@@ -188,7 +212,9 @@ fun DrawNavigationImages(
height: Int height: Int
) { ) {
NavigationImage(padding, width, height) NavigationImage(padding, width, height)
CurrentSpeed(width, height, speed) if (speed != null) {
CurrentSpeed(width, height, speed, maxSpeed)
}
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) { if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
MaxSpeed(width, height, maxSpeed) MaxSpeed(width, height, maxSpeed)
} }
@@ -211,7 +237,8 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
painter = painterResource(id = R.drawable.navigation_48px), painter = painterResource(id = R.drawable.navigation_48px),
"Navigation", "Navigation",
tint = color.copy(alpha = 0.7f), tint = color.copy(alpha = 0.7f),
modifier = Modifier.size(imageSize.dp, imageSize.dp) modifier = Modifier
.size(imageSize.dp, imageSize.dp)
.scale(scaleX = 1f, scaleY = 0.7f), .scale(scaleX = 1f, scaleY = 0.7f),
) )
} }
@@ -221,7 +248,8 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
private fun CurrentSpeed( private fun CurrentSpeed(
width: Int, width: Int,
height: Int, height: Int,
speed: Float? curSpeed: Float,
maxSpeed: Int
) { ) {
val radius = 32 val radius = 32
Box( Box(
@@ -234,7 +262,8 @@ private fun CurrentSpeed(
) { ) {
val textMeasurerSpeed = rememberTextMeasurer() val textMeasurerSpeed = rememberTextMeasurer()
val textMeasurerKm = rememberTextMeasurer() val textMeasurerKm = rememberTextMeasurer()
val speed = (speed!! * 3.6).toInt().toString() val speed = (curSpeed * 3.6).toInt().toString()
val kmh = "km/h" val kmh = "km/h"
val styleSpeed = TextStyle( val styleSpeed = TextStyle(
fontSize = 22.sp, fontSize = 22.sp,
@@ -245,10 +274,10 @@ private fun CurrentSpeed(
fontSize = 12.sp, fontSize = 12.sp,
color = Color.White, color = Color.White,
) )
val textLayoutSpeed = remember(speed) { val textLayoutSpeed = remember(speed, maxSpeed) {
textMeasurerSpeed.measure(speed, styleSpeed) textMeasurerSpeed.measure(speed, styleSpeed)
} }
val textLayoutKm = remember(kmh) { val textLayoutKm = remember(kmh, maxSpeed) {
textMeasurerSpeed.measure(kmh, styleKm) textMeasurerSpeed.measure(kmh, styleKm)
} }
Canvas(modifier = Modifier.fillMaxSize()) { Canvas(modifier = Modifier.fillMaxSize()) {

View File

@@ -34,6 +34,7 @@ import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@@ -60,7 +61,7 @@ class NavigationScreen(
val observer = Observer<String> { route -> val observer = Observer<String> { route ->
if (route.isNotEmpty()) { if (route.isNotEmpty()) {
navigationType = NavigationType.NAVIGATION navigationType = NavigationType.NAVIGATION
routeModel.startNavigation(route) routeModel.startNavigation(route, carContext)
surfaceRenderer.setRouteData() surfaceRenderer.setRouteData()
invalidate() invalidate()
} }
@@ -92,6 +93,16 @@ class NavigationScreen(
var speedCameras = listOf<Elements>() var speedCameras = listOf<Elements>()
val speedObserver = Observer<List<Elements>> { cameras -> val speedObserver = Observer<List<Elements>> { cameras ->
speedCameras = cameras speedCameras = cameras
val coordinates = mutableListOf<List<Double>>()
val 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")
surfaceRenderer.speedCamerasData.value =speedData
} }
init { init {
@@ -339,7 +350,7 @@ class NavigationScreen(
private fun settingsAction(): Action { private fun settingsAction(): Action {
return Action.Builder() return Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_applications_48px)) .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
.setOnClickListener { .setOnClickListener {
screenManager.push(SettingsScreen(carContext)) screenManager.push(SettingsScreen(carContext))
} }
@@ -472,7 +483,7 @@ class NavigationScreen(
private fun updateSpeedCamera(location: Location) { private fun updateSpeedCamera(location: Location) {
if (lastCameraSearch++ % 100 == 0) { if (lastCameraSearch++ % 100 == 0) {
viewModel.getSpeedCameras(location) viewModel.getSpeedCameras(location, 5.0)
} }
if (speedCameras.isNotEmpty()) { if (speedCameras.isNotEmpty()) {
updateDistance(location) updateDistance(location)

View File

@@ -54,13 +54,13 @@ class PlaceListScreen(
} }
init { init {
if (category == Constants.RECENT) { if (category == RECENT) {
viewModel.places.observe(this, observer) viewModel.places.observe(this, observer)
} }
if (category == Constants.CONTACTS) { if (category == CONTACTS) {
viewModel.contactAddress.observe(this, observerAddress) viewModel.contactAddress.observe(this, observerAddress)
} }
if (category == Constants.FAVORITES) { if (category == FAVORITES) {
viewModel.favorites.observe(this, observer) viewModel.favorites.observe(this, observer)
} }
loadPlaces() loadPlaces()
@@ -84,7 +84,7 @@ class PlaceListScreen(
places.forEach { places.forEach {
val row = Row.Builder() val row = Row.Builder()
.setImage(contactIcon(it.avatar, it.category)) .setImage(contactIcon(it.avatar, it.category))
.setTitle(it.name!!) .setTitle("${it.street!!} ${it.city}")
.setOnClickListener { .setOnClickListener {
val place = Place( val place = Place(
0, 0,
@@ -152,7 +152,7 @@ class PlaceListScreen(
.setIcon( .setIcon(
RouteCarModel().createCarIcon( RouteCarModel().createCarIcon(
carContext, carContext,
R.drawable.ic_pan_24 R.drawable.ic_close_white_24dp
) )
) )
.setOnClickListener { .setOnClickListener {
@@ -167,7 +167,7 @@ class PlaceListScreen(
.build() .build()
fun contactIcon(avatar: Uri?, category: String?): CarIcon { fun contactIcon(avatar: Uri?, category: String?): CarIcon {
if (category == Constants.RECENT || avatar == null) { if (category == RECENT || avatar == null) {
return CarIcon.Builder( return CarIcon.Builder(
IconCompat.createWithResource( IconCompat.createWithResource(
carContext, R.drawable.ic_place_white_24dp carContext, R.drawable.ic_place_white_24dp

View File

@@ -52,7 +52,7 @@ class RoutePreviewScreen(
val navigationMessage = NavigationMessage(carContext) val navigationMessage = NavigationMessage(carContext)
val observer = Observer<String> { route -> val observer = Observer<String> { route ->
if (route.isNotEmpty()) { if (route.isNotEmpty()) {
routeModel.startNavigation(route) routeModel.startNavigation(route, carContext)
surfaceRenderer.setPreviewRouteData(routeModel) surfaceRenderer.setPreviewRouteData(routeModel)
invalidate() invalidate()
} }

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M190,840L160,810L480,80L800,810L770,840L480,708L190,840Z"/>
</vector>

View File

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

View File

@@ -124,9 +124,12 @@ data class BoundingBox (
object Constants { object Constants {
const val STYLE: String = "https://kouros-online.de/liberty.json" //const val STYLE: String = "https://kouros-online.de/liberty.json"
const val STYLE_DARK: String = "https://kouros-online.de/liberty_night.json"
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty" //const val STYLE_DARK: String = "https://kouros-online.de/liberty_night.json"
const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
const val STYLE_DARK: String = "https://tiles.openfreemap.org/styles/liberty"
const val TAG: String = "Navigation" const val TAG: String = "Navigation"
const val CATEGORIES: String = "Categories" const val CATEGORIES: String = "Categories"
@@ -176,6 +179,7 @@ object Constants {
const val DESTINATION_ARRIVAL_DISTANCE = 40.0 const val DESTINATION_ARRIVAL_DISTANCE = 40.0
val ROUTE_ENGINE = RouteEngine.VALHALLA.name
} }

View File

@@ -16,6 +16,7 @@
package com.kouros.navigation.data package com.kouros.navigation.data
import android.content.Context
import android.location.Location import android.location.Location
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
@@ -36,10 +37,10 @@ abstract class NavigationRepository {
abstract fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String abstract fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String
fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter): Double { fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter, context: Context): Double {
val route = getRoute(currentLocation, location, searchFilter) val route = getRoute(currentLocation, location, searchFilter)
val routeModel = RouteModel() val routeModel = RouteModel()
routeModel.startNavigation(route) routeModel.startNavigation(route, context)
return routeModel.route.summary!!.distance return routeModel.route.summary!!.distance
} }

View File

@@ -2,6 +2,7 @@ package com.kouros.navigation.data
import android.location.Location import android.location.Location
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.osrm.OsrmResponse import com.kouros.navigation.data.osrm.OsrmResponse
import com.kouros.navigation.data.osrm.OsrmRoute import com.kouros.navigation.data.osrm.OsrmRoute
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
@@ -10,6 +11,8 @@ import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.data.valhalla.ValhallaResponse import com.kouros.navigation.data.valhalla.ValhallaResponse
import com.kouros.navigation.data.valhalla.ValhallaRoute import com.kouros.navigation.data.valhalla.ValhallaRoute
import com.kouros.navigation.utils.GeoUtils.createCenterLocation import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
@@ -23,39 +26,51 @@ data class Route(
val summary: Summary?, val summary: Summary?,
val legs: List<Leg>?, val legs: List<Leg>?,
val routeGeoJson: String = "", val routeGeoJson: String = "",
val centerLocation : Location = location(0.0, 0.0), val centerLocation: Location = location(0.0, 0.0),
var currentStep : Int = 0, var currentStep: Int = 0,
val waypoints: List<List<Double>>?, val waypoints: List<List<Double>>?,
) { ) {
data class Builder ( data class Builder(
var routeEngine : Int = RouteEngine.VALHALLA.ordinal, var routeEngine: Int = 0,
var summary: Summary? = null, var summary: Summary? = null,
var legs: List<Leg>? = null, var legs: List<Leg>? = null,
var routeGeoJson: String = "", var routeGeoJson: String = "",
var centerLocation: Location = location(0.0, 0.0), var centerLocation: Location = location(0.0, 0.0),
var waypoints : List<List<Double>>? = null,) { var waypoints: List<List<Double>>? = null,
) {
fun routeType (routeEngine: Int) = apply {this.routeEngine = routeEngine } fun routeType(routeEngine: Int) = apply { this.routeEngine = routeEngine }
fun summary(summary: Summary) = apply { this.summary = summary } fun summary(summary: Summary) = apply { this.summary = summary }
fun legs(legs: List<Leg>) = apply { this.legs = legs } fun legs(legs: List<Leg>) = apply { this.legs = legs }
fun routeGeoJson(routeGeoJson: String) = apply { fun routeGeoJson(routeGeoJson: String) = apply {
this.routeGeoJson = routeGeoJson this.routeGeoJson = routeGeoJson
centerLocation = createCenterLocation(routeGeoJson) centerLocation = createCenterLocation(routeGeoJson)
} }
fun waypoints(waypoints: List<List<Double>>) = apply { this.waypoints = waypoints }
fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine }
fun waypoints(waypoints: List<List<Double>>) = apply { this.waypoints = waypoints }
fun route(route: String) = apply { fun route(route: String) = apply {
if (route.isNotEmpty() && route != "[]") { if (route.isNotEmpty() && route != "[]") {
val gson = GsonBuilder().serializeNulls().create() val gson = GsonBuilder().serializeNulls().create()
if (routeEngine == RouteEngine.VALHALLA.ordinal) { when (this.routeEngine) {
val jsonObject: Map<String, JsonElement> = Json.parseToJsonElement(route).jsonObject RouteEngine.VALHALLA.ordinal -> {
val routeJson = gson.fromJson(jsonObject["trip"].toString(), ValhallaResponse::class.java) val jsonObject: Map<String, JsonElement> =
Json.parseToJsonElement(route).jsonObject
val routeJson =
gson.fromJson(
jsonObject["trip"].toString(),
ValhallaResponse::class.java
)
ValhallaRoute().mapJsonToValhalla(routeJson, this) ValhallaRoute().mapJsonToValhalla(routeJson, this)
} else { }
else -> {
val osrmJson = gson.fromJson(route, OsrmResponse::class.java) val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
OsrmRoute().mapToOsrm(osrmJson, this) OsrmRoute().mapToOsrm(osrmJson, this)
} }
}
} }
} }

View File

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

View File

@@ -1,11 +1,84 @@
package com.kouros.navigation.data.osrm package com.kouros.navigation.data.osrm
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.valhalla.ValhallaResponse import com.kouros.navigation.data.route.Leg
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
import com.kouros.navigation.utils.GeoUtils.decodePolyline
class OsrmRoute { class OsrmRoute {
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) { fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
val waypoints = mutableListOf<List<Double>>()
val summary = Summary()
summary.distance = routeJson.routes.first().distance!!
summary.duration = routeJson.routes.first().duration!!
val steps = mutableListOf<Step>()
var stepIndex = 0
routeJson.routes.first().legs.first().steps.forEach {
if (it.maneuver != null) {
val points = decodePolyline(it.geometry!!, 5)
waypoints.addAll(points)
val maneuver = RouteManeuver(
bearingBefore = it.maneuver!!.bearingBefore ?: 0,
bearingAfter = it.maneuver!!.bearingAfter ?: 0,
type = convertType(it.maneuver!!),
waypoints = points
)
val step = Step( index = stepIndex, name = it.name!!, distance = it.distance!!, duration = it.duration!!, maneuver = maneuver)
steps.add(step)
stepIndex += 1
}
}
val leg = Leg(steps)
builder
.routeType(1)
.summary(summary)
.routeGeoJson(createLineStringCollection(waypoints))
.legs(listOf(leg))
.waypoints(waypoints.toList())
}
fun convertType(maneuver: Maneuver): Int {
var newType = 0
when (maneuver.type) {
ManeuverType.depart.value -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DEPART
}
ManeuverType.arrive.value -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION
}
ManeuverType.continue_.value -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
}
ManeuverType.turn.value -> {
if (maneuver.modifier == "right") {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
}
}
}
return newType
} }
} }
enum class ManeuverType(val value: String) {
turn("turn"),
depart("depart"),
arrive("arrive"),
merge("merge"),
onRamp("on ramp"),
offRamp("off ramp"),
fork("fork"),
endOfRoad("end of road"),
continue_("continue"),
roundAbout("roundabout"),
rotary("rotary"),
roundaboutTurn("roundabout turn"),
notification("notification"),
exitRoundabout("exit roundabout"),
exitRotary("exit rotary")
}

View File

@@ -10,7 +10,10 @@ import java.net.URL
class Overpass { class Overpass {
val overpassUrl = "https://overpass.kumi.systems/api/interpreter" //val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
val overpassUrl = "https://kouros-online.de/overpass/interpreter"
fun getAround(radius: Int, linestring: String) : List<Elements> { fun getAround(radius: Int, linestring: String) : List<Elements> {
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST" httpURLConnection.requestMethod = "POST"
@@ -28,7 +31,7 @@ class Overpass {
|); |);
|out body; |out body;
""".trimMargin() """.trimMargin()
println("way[highway](around:$radius,$linestring)") //println("way[highway](around:$radius,$linestring)")
return overpassApi(httpURLConnection, searchQuery) return overpassApi(httpURLConnection, searchQuery)
} }
@@ -71,7 +74,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: $response") // println("Overpass: $response")
return overpass.elements return overpass.elements
} }
return emptyList() return emptyList()

View File

@@ -245,6 +245,7 @@
"type": "fill", "type": "fill",
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "water", "source-layer": "water",
"maxzoom": 24,
"filter": ["!=", ["get", "brunnel"], "tunnel"], "filter": ["!=", ["get", "brunnel"], "tunnel"],
"paint": {"fill-color": "rgb(158,189,255)"} "paint": {"fill-color": "rgb(158,189,255)"}
}, },
@@ -860,6 +861,7 @@
true, true,
false false
], ],
"layout": {"visibility": "visible"},
"paint": {"fill-pattern": "pedestrian_polygon"} "paint": {"fill-pattern": "pedestrian_polygon"}
}, },
{ {
@@ -1376,11 +1378,13 @@
"type": "line", "type": "line",
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "transportation", "source-layer": "transportation",
"maxzoom": 24,
"filter": [ "filter": [
"all", "all",
["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true], ["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
["==", ["get", "class"], "transit"] ["==", ["get", "class"], "transit"]
], ],
"layout": {"visibility": "visible"},
"paint": { "paint": {
"line-color": "#bbb", "line-color": "#bbb",
"line-width": [ "line-width": [
@@ -2201,7 +2205,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0, 0.6], "text-offset": [0, 0.6],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#666", "text-color": "#666",
@@ -2240,7 +2245,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0, 0.6], "text-offset": [0, 0.6],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#666", "text-color": "#666",
@@ -2279,7 +2285,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0, 0.6], "text-offset": [0, 0.6],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#666", "text-color": "#666",
@@ -2313,7 +2320,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0.9, 0], "text-offset": [0.9, 0],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#2e5a80", "text-color": "#2e5a80",

View File

@@ -137,7 +137,7 @@
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "landuse", "source-layer": "landuse",
"filter": ["==", ["get", "class"], "pitch"], "filter": ["==", ["get", "class"], "pitch"],
"paint": {"fill-color": "#DEE3CD"} "paint": {"fill-color": "rgba(49, 49, 40, 1)"}
}, },
{ {
"id": "landuse_track", "id": "landuse_track",
@@ -247,7 +247,7 @@
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "water", "source-layer": "water",
"filter": ["!=", ["get", "brunnel"], "tunnel"], "filter": ["!=", ["get", "brunnel"], "tunnel"],
"paint": {"fill-color": "rgb(158,189,255)"} "paint": {"fill-color": "rgba(34, 54, 98, 1)"}
}, },
{ {
"id": "landcover_sand", "id": "landcover_sand",
@@ -255,7 +255,7 @@
"source": "openmaptiles", "source": "openmaptiles",
"source-layer": "landcover", "source-layer": "landcover",
"filter": ["==", ["get", "class"], "sand"], "filter": ["==", ["get", "class"], "sand"],
"paint": {"fill-color": "rgba(247, 239, 195, 1)"} "paint": {"fill-color": "rgba(148, 146, 138, 1)"}
}, },
{ {
"id": "aeroway_fill", "id": "aeroway_fill",
@@ -654,7 +654,7 @@
], ],
"layout": {"line-join": "round"}, "layout": {"line-join": "round"},
"paint": { "paint": {
"line-color": "#fff", "line-color": "rgba(74, 64, 64, 1)",
"line-width": [ "line-width": [
"interpolate", "interpolate",
["exponential", 1.2], ["exponential", 1.2],
@@ -706,7 +706,7 @@
], ],
"layout": {"line-join": "round"}, "layout": {"line-join": "round"},
"paint": { "paint": {
"line-color": "#fff4c6", "line-color": "rgba(72, 70, 58, 1)",
"line-width": [ "line-width": [
"interpolate", "interpolate",
["exponential", 1.2], ["exponential", 1.2],
@@ -1154,7 +1154,7 @@
], ],
"layout": {"line-cap": "round", "line-join": "round"}, "layout": {"line-cap": "round", "line-join": "round"},
"paint": { "paint": {
"line-color": "#fff", "line-color": "rgba(55, 53, 53, 1)",
"line-width": [ "line-width": [
"interpolate", "interpolate",
["exponential", 1.2], ["exponential", 1.2],
@@ -1691,7 +1691,7 @@
["match", ["get", "class"], ["path", "pedestrian"], true, false] ["match", ["get", "class"], ["path", "pedestrian"], true, false]
], ],
"paint": { "paint": {
"line-color": "hsl(0,0%,100%)", "line-color": "rgba(107, 102, 102, 1)",
"line-dasharray": [1, 0.3], "line-dasharray": [1, 0.3],
"line-width": [ "line-width": [
"interpolate", "interpolate",
@@ -2208,7 +2208,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0, 0.6], "text-offset": [0, 0.6],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#666", "text-color": "#666",
@@ -2247,7 +2248,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0, 0.6], "text-offset": [0, 0.6],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#666", "text-color": "#666",
@@ -2286,7 +2288,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0, 0.6], "text-offset": [0, 0.6],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#666", "text-color": "#666",
@@ -2320,7 +2323,8 @@
"text-font": ["Noto Sans Italic"], "text-font": ["Noto Sans Italic"],
"text-max-width": 9, "text-max-width": 9,
"text-offset": [0.9, 0], "text-offset": [0.9, 0],
"text-size": 12 "text-size": 12,
"visibility": "none"
}, },
"paint": { "paint": {
"text-color": "#2e5a80", "text-color": "#2e5a80",

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.data package com.kouros.navigation.data.valhalla
enum class ManeuverType(val value: Int) { enum class ManeuverType(val value: Int) {
None(0), None(0),

View File

@@ -8,6 +8,8 @@ import com.kouros.navigation.data.ValhallaLocation
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
private const val routeUrl = "https://kouros-online.de/valhalla/route?json=" private const val routeUrl = "https://kouros-online.de/valhalla/route?json="
class ValhallaRepository : NavigationRepository() { class ValhallaRepository : NavigationRepository() {
override fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String { override fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String {

View File

@@ -1,20 +1,28 @@
package com.kouros.navigation.model package com.kouros.navigation.model
import android.content.Context
import android.location.Location import android.location.Location
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step import androidx.car.app.navigation.model.Step
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.valhalla.ManeuverType
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.invoke
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -43,8 +51,10 @@ open class RouteModel() {
val legs: Leg val legs: Leg
get() = routeState.route!!.legs!!.first() get() = routeState.route!!.legs!!.first()
fun startNavigation(routeString: String) { fun startNavigation(routeString: String, context: Context) {
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE)
val newRoute = Route.Builder() val newRoute = Route.Builder()
.routeEngine(routeEngine)
.route(routeString) .route(routeString)
.build() .build()
this.routeState = routeState.copy( this.routeState = routeState.copy(
@@ -65,15 +75,13 @@ open class RouteModel() {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun updateLocation(location: Location, viewModel: ViewModel) { fun updateLocation(location: Location, viewModel: ViewModel) {
findStep(location) findStep(location)
GlobalScope.launch(Dispatchers.IO) { updateSpeedLimit(location, viewModel)
updateSpeedLimit(location, viewModel)
}
} }
private fun findStep(location: Location) { private fun findStep(location: Location) {
var nearestDistance = 100000.0f var nearestDistance = 100000.0f
for ((index, step) in legs.steps.withIndex()) { for ((index, step) in legs.steps.withIndex()) {
if (index >= route.currentStep && nearestDistance > 0) { if (index >= route.currentStep) {
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
if (wayIndex >= step.waypointIndex) { if (wayIndex >= step.waypointIndex) {
val distance = location.distanceTo(location(waypoint[0], waypoint[1])) val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
@@ -83,22 +91,31 @@ open class RouteModel() {
step.waypointIndex = wayIndex step.waypointIndex = wayIndex
} }
} }
if (nearestDistance == 0F) {
break
}
} }
} }
if (nearestDistance == 0F) {
break
}
} }
//println("Current Index ${route.currentStep} WayPoint: ${route.currentStep().waypointIndex}")
} }
fun updateSpeedLimit(location: Location, viewModel: ViewModel) { fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
// speed limit CoroutineScope(Dispatchers.IO).launch {
val distance = routeState.lastSpeedLocation.distanceTo(location) // speed limit
if (distance > 500 || routeState.lastSpeedIndex < route.currentStep) { val distance = routeState.lastSpeedLocation.distanceTo(location)
routeState = routeState.copy(lastSpeedIndex = route.currentStep) if (distance > 500 || routeState.lastSpeedIndex < route.currentStep) {
routeState = routeState.copy(lastSpeedLocation = location) routeState = routeState.copy(lastSpeedIndex = route.currentStep)
val elements = viewModel.getMaxSpeed(location) routeState = routeState.copy(lastSpeedLocation = location)
elements.forEach { val elements = viewModel.getMaxSpeed(location)
if (it.tags.name != null && it.tags.maxspeed != null) { elements.forEach {
val speed = it.tags.maxspeed!!.toInt() if (it.tags.name != null && it.tags.maxspeed != null) {
routeState = routeState.copy(maxSpeed = speed) val speed = it.tags.maxspeed!!.toInt()
routeState = routeState.copy(maxSpeed = speed)
}
} }
} }
} }
@@ -148,11 +165,9 @@ open class RouteModel() {
val maneuverType = step.maneuver.type val maneuverType = step.maneuver.type
val distanceLeft = leftStepDistance() val distanceLeft = leftStepDistance()
var text = "" var text = ""
when (distanceLeft) { when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> { in 0.0..NEXT_STEP_THRESHOLD -> {
} }
else -> { else -> {
if (step.name.isNotEmpty()) { if (step.name.isNotEmpty()) {
text = step.name text = step.name
@@ -174,11 +189,13 @@ open class RouteModel() {
fun travelLeftTime(): Double { fun travelLeftTime(): Double {
var timeLeft = 0.0 var timeLeft = 0.0
// time for next step until end step
for (i in route.currentStep + 1..<legs.steps.size) { for (i in route.currentStep + 1..<legs.steps.size) {
val step = legs.steps[i] val step = legs.steps[i]
timeLeft += step.duration timeLeft += step.duration
} }
val step = route.nextStep() // time for current step
val step = route.currentStep()
val curTime = step.duration val curTime = step.duration
val percent = val percent =
100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size) 100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
@@ -220,8 +237,8 @@ open class RouteModel() {
val curDistance = step.distance val curDistance = step.distance
val percent = val percent =
100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size) 100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
val time = curDistance * percent / 100 val distance = curDistance * percent / 100
leftDistance += time leftDistance += distance
return leftDistance return leftDistance
} }

View File

@@ -109,7 +109,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
repository.getRouteDistance( repository.getRouteDistance(
location, location,
plLocation, plLocation,
getSearchFilter(context) getSearchFilter(context), context
) )
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
@@ -134,7 +134,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = val distance =
repository.getRouteDistance(location, plLocation, getSearchFilter(context)) repository.getRouteDistance(location, plLocation, getSearchFilter(context), context)
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
favorites.postValue(results) favorites.postValue(results)
@@ -265,9 +265,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun getSpeedCameras(location: Location) { fun getSpeedCameras(location: Location, radius : Double) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities("highway", "speed_camera", location, 5.0) val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
val distAmenities = mutableListOf<Elements>() val distAmenities = mutableListOf<Elements>()
amenities.forEach { amenities.forEach {
val plLocation = val plLocation =
@@ -380,7 +380,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (place in results) { for (place in results) {
val plLocation = location(place.longitude, place.latitude) val plLocation = location(place.longitude, place.latitude)
val distance = val distance =
repository.getRouteDistance(location, plLocation, getSearchFilter(context)) repository.getRouteDistance(location, plLocation, getSearchFilter(context), context)
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -10,6 +10,7 @@ import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.dsl.addFeature import org.maplibre.spatialk.geojson.dsl.addFeature
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
import org.maplibre.spatialk.geojson.dsl.buildLineString import org.maplibre.spatialk.geojson.dsl.buildLineString
import org.maplibre.spatialk.geojson.dsl.buildMultiPoint
import org.maplibre.spatialk.geojson.toJson import org.maplibre.spatialk.geojson.toJson
import org.maplibre.turf.TurfMeasurement import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc import org.maplibre.turf.TurfMisc
@@ -32,8 +33,9 @@ object GeoUtils {
return newLocation return newLocation
} }
fun decodePolyline(encoded: String, vararg precisionOptional: Int): List<List<Double>> {
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6 fun decodePolyline(encoded: String, precision: Int = 6,): List<List<Double>> {
//val precisionOptional = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
val factor = 10.0.pow(precision) val factor = 10.0.pow(precision)
var lat = 0 var lat = 0

View File

@@ -4,7 +4,12 @@ import android.content.Context
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import androidx.core.content.edit import androidx.core.content.edit
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
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.ViewModel
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
@@ -20,6 +25,14 @@ import kotlin.time.Duration.Companion.seconds
object NavigationUtils { object NavigationUtils {
fun getRouteEngine(context: Context): ViewModel {
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE)
return when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
else -> ViewModel(OsrmRepository())
}
}
fun getBooleanKeyValue(context: Context, key: String): Boolean { fun getBooleanKeyValue(context: Context, key: String): Boolean {
return context return context
.getSharedPreferences( .getSharedPreferences(
@@ -43,13 +56,13 @@ object NavigationUtils {
} }
} }
fun getIntKeyValue(context: Context, key: String): Int { fun getIntKeyValue(context: Context, key: String, default: Int = 0): Int {
return context return context
.getSharedPreferences( .getSharedPreferences(
SHARED_PREF_KEY, SHARED_PREF_KEY,
Context.MODE_PRIVATE Context.MODE_PRIVATE
) )
.getInt(key, 0) .getInt(key, default)
} }
fun setIntKeyValue(context: Context, `val`: Int, key: String) { fun setIntKeyValue(context: Context, `val`: Int, key: String) {

View File

@@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal"> android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M190,840L160,810L480,80L800,810L770,840L480,708L190,840ZM258,742L480,644L702,742L480,228L258,742ZM480,644L480,644L480,644L480,644Z"/> android:pathData="M190,840L160,810L480,80L800,810L770,840L480,708L190,840Z"/>
</vector> </vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M388,880L368,754Q349,747 328,735Q307,723 291,710L173,764L80,600L188,521Q186,512 185.5,500.5Q185,489 185,480Q185,471 185.5,459.5Q186,448 188,439L80,360L173,196L291,250Q307,237 328,225Q349,213 368,207L388,80L572,80L592,206Q611,213 632.5,224.5Q654,236 669,250L787,196L880,360L772,437Q774,447 774.5,458.5Q775,470 775,480Q775,490 774.5,501Q774,512 772,522L880,600L787,764L669,710Q653,723 632.5,735.5Q612,748 592,754L572,880L388,880ZM436,820L524,820L538,708Q571,700 600.5,683Q630,666 654,642L760,688L800,616L706,547Q710,530 712.5,513.5Q715,497 715,480Q715,463 713,446.5Q711,430 706,413L800,344L760,272L654,318Q631,292 602,274.5Q573,257 538,252L524,140L436,140L422,252Q388,259 358.5,276Q329,293 306,318L200,272L160,344L254,413Q250,430 247.5,446.5Q245,463 245,480Q245,497 247.5,513.5Q250,530 254,547L160,616L200,688L306,642Q330,666 359.5,683Q389,700 422,708L436,820ZM480,610Q534,610 572,572Q610,534 610,480Q610,426 572,388Q534,350 480,350Q426,350 388,388Q350,426 350,480Q350,534 388,572Q426,610 480,610ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M452,674L508,674L518,620Q538,614 552,605Q566,596 578,584L640,603L666,549L619,519Q623,498 623,480Q623,462 619,441L666,411L640,357L578,376Q566,364 552,355Q538,346 518,340L508,286L452,286L442,340Q422,346 408,355Q394,364 382,376L320,357L294,411L341,441Q337,462 337,480Q337,498 341,519L294,549L320,603L382,584Q394,596 408,605Q422,614 442,620L452,674ZM480,565Q444,565 419.5,540.5Q395,516 395,480Q395,444 419.5,419.5Q444,395 480,395Q516,395 540.5,419.5Q565,444 565,480Q565,516 540.5,540.5Q516,565 480,565ZM180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,180Q780,180 780,180Q780,180 780,180L180,180Q180,180 180,180Q180,180 180,180L180,780Q180,780 180,780Q180,780 180,780ZM180,180L180,180Q180,180 180,180Q180,180 180,180L180,780Q180,780 180,780Q180,780 180,780L180,780Q180,780 180,780Q180,780 180,780L180,180Q180,180 180,180Q180,180 180,180Z"/>
</vector>