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

@@ -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_SNAP_CORRECTION
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
class NavigationSession : Session(), NavigationScreen.Listener {
@@ -67,6 +69,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
}
}
val navigationViewModel = ViewModel(ValhallaRepository())
init {
val lifecycle: Lifecycle = lifecycle
@@ -78,7 +81,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
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)
== PackageManager.PERMISSION_GRANTED && !useContacts
@@ -116,6 +119,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
carContext,
surfaceRenderer,
location,
navigationViewModel,
// TODO: Uri
)
) { obj: Any? ->

View File

@@ -28,7 +28,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
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.cameraState
import com.kouros.navigation.car.map.getPaddingValues
@@ -191,7 +191,7 @@ class SurfaceRenderer(
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
val currentSpeed: Float? by speed.observeAsState()
if (viewStyle == ViewStyle.VIEW) {
DrawImage(paddingValues, currentSpeed, width, height)
DrawNavigationImages(paddingValues, currentSpeed, routeModel.routeState.maxSpeed, width, height)
}
LaunchedEffect(position, viewStyle) {
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.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
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.utils.NavigationUtils.getBooleanKeyValue
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.CameraState
import org.maplibre.compose.camera.rememberCameraState
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.image
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.rememberGeoJsonSource
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.dsl.FeatureBuilder
import org.maplibre.spatialk.geojson.dsl.FeatureCollectionBuilder
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
@Composable
@@ -187,9 +177,12 @@ fun BuildingLayer(tiles: Source) {
}
@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)
Speed(width, height, speed)
CurrentSpeed(width, height, speed)
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
MaxSpeed(width, height, maxSpeed)
}
}
@Composable
@@ -215,7 +208,7 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
}
@Composable
private fun Speed(
private fun CurrentSpeed(
width: Int,
height: Int,
speed: Float?
@@ -235,6 +228,7 @@ private fun Speed(
val kmh = "km/h"
val styleSpeed = TextStyle(
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
)
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
fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS)

View File

@@ -74,7 +74,7 @@ class RouteCarModel() : RouteModel() {
return step
}
fun travelEstimate(): TravelEstimate {
fun travelEstimate(carContext: CarContext): TravelEstimate {
val timeLeft = travelLeftTime()
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
@@ -88,7 +88,7 @@ class RouteCarModel() : RouteModel() {
arrivalTime(),
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(
leftDistance,
displayUnit
@@ -102,9 +102,12 @@ class RouteCarModel() : RouteModel() {
)
.setRemainingTimeColor(CarColor.YELLOW)
.setRemainingDistanceColor(CarColor.RED)
//.setTripText(createCarText(carContext,R.string.navigate))
//.setTripIcon(createCarIcon(carContext, R.drawable.navigation_48px))
.build()
if (routeState.travelMessage.isNotEmpty()) {
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
travelBuilder.setTripText(CarText.create(routeState.travelMessage))
}
return travelBuilder.build()
}
fun createString(
@@ -123,12 +126,12 @@ class RouteCarModel() : RouteModel() {
}
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(createAlert(carContext, distance, maxSpeed))
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(createAlert(carContext, distance, maxSpeed))
}
fun createAlert(carContext: CarContext, distance: Double, maxSpeed: String?): Alert {
val title = createCarText(carContext,R.string.speed_camera)
val title = createCarText(carContext, R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
val icon = CarIcon.ALERT
@@ -144,6 +147,7 @@ class RouteCarModel() : RouteModel() {
.addAction(dismissAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) {
}
override fun onDismiss() {
}
}).build()
@@ -155,8 +159,8 @@ class RouteCarModel() : RouteModel() {
flags: Int
): Action {
return Action.Builder()
.setOnClickListener { }
.setTitle(createCarText(carContext,titleRes))
.setOnClickListener { }
.setTitle(createCarText(carContext, titleRes))
.setFlags(flags)
.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.FUEL_STATION
import com.kouros.navigation.data.Constants.PHARMACY
import com.kouros.navigation.model.ViewModel
class CategoriesScreen(
private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer,
private val location: Location,
private val viewModel: ViewModel
) : Screen(carContext) {
var categories: List<Category> = listOf(
@@ -46,7 +48,8 @@ class CategoriesScreen(
carContext,
surfaceRenderer,
location,
it.id
it.id,
viewModel
)
) { obj: Any? ->
if (obj != null) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,34 +27,14 @@ import java.net.URL
import kotlinx.serialization.json.Json
class NavigationRepository {
abstract class NavigationRepository {
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/"
// Road classes from highest to lowest are:
// motorway, trunk, primary, secondary, tertiary, unclassified, residential, service_other.
// exclude_toll
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)
}
abstract fun getRoute(currentLocation: Location, location: Location, searchFilter: SearchFilter): String
fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter): Double {
val route = getRoute(currentLocation, location, searchFilter)
@@ -98,7 +78,7 @@ class NavigationRepository {
return places
}
private fun fetchUrl(url: String, authenticator : Boolean): String {
fun fetchUrl(url: String, authenticator : Boolean): String {
try {
if (authenticator) {
Authenticator.setDefault(object : Authenticator() {

View File

@@ -75,8 +75,8 @@ data class Route(
fun route(route: String) = apply {
if (route.isNotEmpty() && route != "[]") {
val gson = GsonBuilder().serializeNulls().create()
val valhalla = gson.fromJson(route, ValhallaJson::class.java)
trip = valhalla.trip
val routeJson = gson.fromJson(route, ValhallaJson::class.java)
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 com.google.gson.GsonBuilder
import com.kouros.navigation.utils.GeoUtils.getBoundingBox2
import com.kouros.navigation.utils.GeoUtils.getOverpassBbox
import com.kouros.navigation.utils.NavigationUtils
import kotlinx.serialization.json.Json
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
@@ -12,8 +11,33 @@ import java.net.URL
class Overpass {
val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
fun getAmenities(type: String, category: String, location: Location) : List<Elements> {
val boundingBox = getOverpassBbox(location, 5.0)
fun getAround(radius: Int, linestring: String) : List<Elements> {
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
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
@@ -32,8 +56,11 @@ class Overpass {
|);
|out body;
""".trimMargin()
return overpassApi(httpURLConnection, searchQuery)
}
// Send the JSON we created
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
// Send the JSON we created
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(searchQuery)
outputStreamWriter.flush()
@@ -44,9 +71,9 @@ class Overpass {
.use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
println("Overpass: $type $response")
//println("Overpass: $response")
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.Step
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.ManeuverType
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.location
import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
@@ -21,14 +21,19 @@ open class RouteModel() {
data class RouteState(
val route: Route? = null,
val isNavigating: Boolean = false,
var destination: Place = Place(),
val destination: Place = Place(),
val arrived: Boolean = false,
var maneuverType: Int = 0,
val maneuverType: Int = 0,
var currentShapeIndex: Int = 0,
var distanceToStepEnd: Float = 0F,
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()
@@ -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 newShapeIndex = -1
// find nearest waypoint and current shape index
@@ -77,6 +83,24 @@ open class RouteModel() {
// find maneuver
// calculate distance to step end
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) {
@@ -127,7 +151,7 @@ open class RouteModel() {
maneuverType = relevantManeuver.type
}
val maneuverIconPair = maneuverIcon(maneuverType)
routeState.maneuverType = maneuverIconPair.first
routeState = routeState.copy(maneuverType = maneuverIconPair.first)
// Construct and return the final StepData object
return StepData(
streetName,
@@ -149,6 +173,7 @@ open class RouteModel() {
when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
}
else -> {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
@@ -296,8 +321,8 @@ open class RouteModel() {
fun hasArrived(type: Int): Boolean {
// return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value
return type == ManeuverType.DestinationRight.value
// return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value
return type == ManeuverType.DestinationRight.value
|| type == ManeuverType.Destination.value
|| type == ManeuverType.DestinationLeft.value
}

View File

@@ -251,7 +251,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun getAmenities(category: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities("amenity", category, location)
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
val distAmenities = mutableListOf<Elements>()
amenities.forEach {
val plLocation =
@@ -267,7 +267,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun getSpeedCameras(location: Location) {
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>()
amenities.forEach {
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) {
place.category = Constants.FAVORITES
savePlace(place)

View File

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