Restaurants

This commit is contained in:
Dimitris
2026-05-05 14:27:00 +02:00
parent 202c2dfcea
commit 25ba7d7bf6
19 changed files with 134 additions and 83 deletions
@@ -755,7 +755,11 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
val duration = Duration.between(current, lastTrafficDate)
if (showTraffic && duration.abs().seconds > TRAFFIC_UPDATE) {
lastTrafficDate = current
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
navigationViewModel.loadTraffic(
carContext,
location,
surfaceRenderer.carOrientation
)
}
}
@@ -28,12 +28,20 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.data.R
import com.kouros.navigation.data.ClosedColor
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.HeavyColor
import com.kouros.navigation.data.LaneColor
import com.kouros.navigation.data.NavigationColorDark
import com.kouros.navigation.data.NavigationColorLight
import com.kouros.navigation.data.PharmacyColor
import com.kouros.navigation.data.QueuingColor
import com.kouros.navigation.data.RoadworksColor
import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SlowColor
import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.data.StationaryColor
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.utils.GeoUtils.createEndCollection
import com.kouros.navigation.utils.GeoUtils.createPointCollection
@@ -180,6 +188,12 @@ fun RouteLayer(routeData: String?) {
fun TrafficLayer(trafficData: Map<String, String>) {
trafficData.forEach {
val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value))
LineLayer(
id = "traffic-${it.key}-casing",
source = traffic,
color = const(Color.White),
width = routeLineWidth(base = 2.dp, isCasing = true),
)
LineLayer(
id = "traffic-${it.key}",
source = traffic,
@@ -289,13 +303,13 @@ fun trafficImage(key: String): Expression<ImageValue> {
fun trafficColor(key: String): Expression<ColorValue> {
when (key) {
"queuing" -> return const(Color(0xFFC46E53))
"slow" -> return const(Color(0xFFC43E3E))
"stationary" -> return const(Color(0xFF910A0A))
"heavy" -> return const(Color(0xFF6B0404))
"roadworks" -> return const(Color(0xFFDAA707))
"closed" -> return const(Color(0xFF210C0F))
"lane" -> return const(Color(0xFFEFBAC1))
"queuing" -> return const(QueuingColor)
"slow" -> return const(SlowColor)
"stationary" -> return const(StationaryColor)
"heavy" -> return const(HeavyColor)
"roadworks" -> return const(RoadworksColor)
"closed" -> return const(ClosedColor)
"lane" -> return const(LaneColor)
}
return const(Color.Blue)
}
@@ -306,11 +320,14 @@ fun AmenityLayer(routeData: String?) {
var color = const(Color.Red)
var img = image(painterResource(R.drawable.local_pharmacy_24px), drawAsSdf = true)
if (routeData.contains(Constants.CHARGING_STATION)) {
color = const(Color(0xFF054603))
color = const(PharmacyColor)
img = image(painterResource(R.drawable.ev_station_24px), drawAsSdf = true)
} else if (routeData.contains(Constants.FUEL_STATION)) {
color = const(Color.Blue)
img = image(painterResource(R.drawable.local_gas_station_24), drawAsSdf = true)
} else if (routeData.contains(Constants.RESTAURANT)) {
color = const(Color.Magenta)
img = image(painterResource(R.drawable.restaurant_24px), drawAsSdf = true)
}
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
SymbolLayer(
@@ -4,6 +4,8 @@ import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
import androidx.car.app.model.GridItem
import androidx.car.app.model.GridTemplate
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
@@ -16,6 +18,7 @@ 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.data.Constants.RESTAURANT
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.model.NavigationViewModel
@@ -30,22 +33,18 @@ class CategoriesScreen(
var categories: List<Category> = listOf(
Category(id = FUEL_STATION, name = carContext.getString(R.string.fuel_station)),
Category(id = PHARMACY, name = carContext.getString(R.string.pharmacy)),
Category(id = CHARGING_STATION, name = carContext.getString(R.string.charging_station))
Category(id = CHARGING_STATION, name = carContext.getString(R.string.charging_station)),
Category(id = RESTAURANT, name = carContext.getString(R.string.restaurant))
)
init {
}
override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder()
.setNoItemsMessage("No categories to show")
categories.forEach {
itemListBuilder.addItem(
Row.Builder()
.setTitle(it.name)
GridItem.Builder()
.setImage(carIcon(carContext, it.id, -1))
.setTitle(it.name)
.setOnClickListener {
category = it.id
screenManager
@@ -55,7 +54,7 @@ class CategoriesScreen(
surfaceRenderer,
category,
navigationViewModel,
)
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
@@ -63,20 +62,19 @@ class CategoriesScreen(
}
}
}
.setBrowsable(true)
.build()
)
}
surfaceRenderer.viewStyle = ViewStyle.AMENITY_VIEW
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.category_title))
.build()
return ListTemplate.Builder()
.setHeader(header)
return GridTemplate.Builder()
.setHeader(
Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.category_title))
.build()
)
.setSingleList(itemListBuilder.build())
.build()
}
@@ -88,7 +86,7 @@ fun carIcon(context: CarContext, category: String, index: Int): CarIcon {
CHARGING_STATION -> R.drawable.ev_station_24px
FUEL_STATION -> R.drawable.local_gas_station_24
PHARMACY -> R.drawable.local_pharmacy_24px
else -> R.drawable.ic_place_white_24dp
else -> R.drawable.restaurant_24px
}
return CarIcon.Builder(IconCompat.createWithResource(context, resId)).build()
} else {
@@ -31,6 +31,7 @@ import com.kouros.navigation.data.Constants
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.data.Constants.RESTAURANT
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.overpass.Elements
@@ -52,7 +53,6 @@ class CategoryScreen(
) : Screen(carContext), CategoryObserverCallback {
val repository = getSettingsRepository(carContext)
val settingsViewModel = getSettingsViewModel(carContext)
@@ -77,7 +77,6 @@ class CategoryScreen(
})
navigationViewModel.elements.observe(this, categoryObserver)
//navigationViewModel.getAmenities(carContext, category, surfaceRenderer.lastLocation)
}
override fun onGetTemplate(): Template {
@@ -94,12 +93,10 @@ class CategoryScreen(
)
)
elements.forEach {
if (it.tags.operator != null) {
if (index++ < listLimit) {
listBuilder.addItem(
createItem(it, category, index)
)
}
if (index++ < listLimit) {
listBuilder.addItem(
createItem(it, category, index)
)
}
}
}
@@ -133,7 +130,7 @@ class CategoryScreen(
CHARGING_STATION -> R.string.charging_station
FUEL_STATION -> R.string.fuel_station
PHARMACY -> R.string.pharmacy
else -> R.string.no_places
else -> R.string.restaurant
}
return carContext.getString(resId)
}
@@ -154,10 +151,10 @@ class CategoryScreen(
}
.setTitle(name)
.setImage(carIcon(carContext, category, index))
if (category == CHARGING_STATION) {
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
} else {
row.addText(carText(" ${it.e5} / ${it.e10}${it.tags.openingHours}"))
when (category) {
CHARGING_STATION -> row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
FUEL_STATION -> row.addText(carText("${it.e5} / ${it.e10}${it.tags.openingHours}"))
RESTAURANT, PHARMACY ->row.addText(carText(it.tags.openingHours))
}
if (it.distance < 1000) {
row.addText("${(it.distance).toInt()} m")
@@ -3,7 +3,6 @@ package com.kouros.navigation.car.screen
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.car.app.CarContext
import androidx.car.app.CarToast
@@ -13,7 +12,6 @@ import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
import androidx.car.app.model.DurationSpan
import androidx.car.app.model.ForegroundCarColorSpan
@@ -26,7 +24,6 @@ import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.versioning.CarAppApiLevels
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
@@ -35,7 +32,6 @@ import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.data.route.Routes
@@ -377,7 +373,6 @@ class RoutePreviewScreen(
surfaceRenderer.carOrientation
)
}
}
enum class RoutePreviewType {
@@ -50,7 +50,7 @@ fun createNumberIcon(category: String, number: String): IconCompat {
CHARGING_STATION -> Color.GREEN
FUEL_STATION -> Color.BLUE
PHARMACY -> Color.RED
else -> Color.WHITE
else -> Color.MAGENTA
}
paint.color = color
canvas.drawCircle(size / 2f, size / 2f, size / 2f, paint)
@@ -11,4 +11,14 @@ val SpeedColor = Color(0xFF262525)
val MaxSpeedColor = Color(0xFFB71515)
val PlaceColor = Color(0xFF868005)
val PlaceColor = Color(0xFF868005)
val QueuingColor = Color(0xFFC46E53)
val SlowColor = Color(0xFFC43E3E)
val StationaryColor = Color(0xFF910A0A)
val HeavyColor = Color(0xFF6B0404)
val RoadworksColor = Color(0xFFDAA707)
val ClosedColor = Color(0xFF280C10)
val LaneColor = Color(0xFFEFBAC1)
val PharmacyColor = Color(0xFF054603)
@@ -122,6 +122,8 @@ object Constants {
const val CHARGING_STATION: String ="charging_station"
const val RESTAURANT: String ="restaurant"
/** The initial location to use as an anchor for searches. */
val homeVogelhart = location(11.5793748, 48.185749)
val homeHohenwaldeck = location( 11.594322, 48.1164817)
@@ -75,7 +75,7 @@ abstract class NavigationRepository {
"Accept",
"application/json"
) // The format of response we want to get from the server
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de")
httpURLConnection.setRequestProperty("User-Agent", "email=online@kouros-online.de")
httpURLConnection.requestMethod = "GET"
val responseCode = httpURLConnection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
@@ -136,6 +136,7 @@ class Overpass {
}
}
Log.w("OverpassApi", searchQuery)
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
val response = connection.inputStream.bufferedReader().use { it.readText() }
@@ -21,7 +21,7 @@ data class Tags(
val capacity: String = "",
val motorcar: String = "",
val network: String = "",
val openingHours: String = "",
@SerializedName("opening_hours") val openingHours: String = "",
val operator: String = "",
val operatorShort: String = "",
val operatorWikidata: String = "",
@@ -19,7 +19,6 @@ import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Places
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.data.fuel.FuelPrice
import com.kouros.navigation.data.fuel.FuelPrices
import com.kouros.navigation.data.fuel.Station
import com.kouros.navigation.data.fuel.Stations
@@ -28,15 +27,14 @@ import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.ElementSearch
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.overpass.Overpass
import com.kouros.navigation.repository.SettingsRepository
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.GeoUtils.transferCoordinates
import com.kouros.navigation.utils.bearingPositive
import com.kouros.navigation.utils.countryCodeSpeedLimit
import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.isNumeric
import com.kouros.navigation.utils.location
import com.kouros.navigation.utils.settingsViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -220,7 +218,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
* Fetches traffic incident data and categorizes by severity.
* Posts categorized traffic map to traffic LiveData.
*/
fun loadTraffic(context: Context, currentLocation: Location, carOrientation: Float) {
fun loadTraffic(
context: Context,
currentLocation: Location,
carOrientation: Float,
) {
viewModelScope.launch(Dispatchers.IO) {
try {
val data = repository.getTraffic(
@@ -400,18 +402,8 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
fun getAmenities(carContext: Context, category: String, location: Location, lastFuelUpdate: Long = 0) {
viewModelScope.launch(Dispatchers.IO) {
val repository = getSettingsRepository(carContext)
var fuelPrices: List<Station>
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
val now = System.currentTimeMillis()
if (category == Constants.FUEL_STATION && ((now - lastFuelUpdate) > TANKER_KOENIG_DELAY)) {
fuelPrices = FuelPrices().getFuelPrices(location, 5)
repository.setLastFuelPrices(System.currentTimeMillis())
repository.setFuelPrices(gson.toJson(Stations(fuelPrices)))
} else {
val fuels = repository.fuelPricesFlow.first()
fuelPrices =
gson.fromJson(fuels, Stations::class.java).stations
}
val fuelPrices = fuelStations(category, lastFuelUpdate, location, repository)
val distAmenities = mutableListOf<Elements>()
amenities.forEach {
val plLocation =
@@ -428,6 +420,28 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
}
}
private suspend fun fuelStations(
category: String,
lastFuelUpdate: Long,
location: Location,
repository: SettingsRepository
): List<Station> {
var fuelPrices = emptyList<Station>()
if (category == Constants.FUEL_STATION) {
val now = System.currentTimeMillis()
if ((now - lastFuelUpdate) > TANKER_KOENIG_DELAY) {
fuelPrices = FuelPrices().getFuelPrices(location, 5)
repository.setLastFuelPrices(System.currentTimeMillis())
repository.setFuelPrices(gson.toJson(Stations(fuelPrices)))
} else {
val fuels = repository.fuelPricesFlow.first()
fuelPrices =
gson.fromJson(fuels, Stations::class.java).stations
}
}
return fuelPrices
}
fun addFuelPrice(fuel: Elements, fuelPrices: List<Station>, location: Location) {
fuelPrices.forEach {
if (fuel.tags.name.equals(it.brand, ignoreCase = true)) {
@@ -1,7 +1,6 @@
package com.kouros.navigation.utils
import android.location.Location
import android.util.Log
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import org.maplibre.geojson.FeatureCollection
@@ -24,6 +23,8 @@ import kotlin.math.sin
object GeoUtils {
const val EARTH_RADIUS = 6371.0 // in km
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
val newLocation = Location(location)
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
@@ -151,11 +152,10 @@ object GeoUtils {
* @return latMin, latMax, lngMin, lngMax
*/
fun calculateSquareRadius(lat: Double, lng: Double, radius: Double): String {
val earthRadius = 6371.0 // earth radius in km
val latMin = lat - toDegrees(radius / earthRadius)
val latMax = lat + toDegrees(radius / earthRadius)
val lngMin = lng - toDegrees(radius / earthRadius / cos(toRadians(lat)))
val lngMax = lng + toDegrees(radius / earthRadius / cos(toRadians(lat)))
val latMin = lat - toDegrees(radius / EARTH_RADIUS)
val latMax = lat + toDegrees(radius / EARTH_RADIUS)
val lngMin = lng - toDegrees(radius / EARTH_RADIUS / cos(toRadians(lat)))
val lngMax = lng + toDegrees(radius / EARTH_RADIUS / cos(toRadians(lat)))
return "$lngMin,$latMin,$lngMax,$latMax"
}
@@ -165,11 +165,10 @@ object GeoUtils {
lon: Double,
radius: Double
): String {
val earthRadius = 6371.0
val maxLat = lat + toDegrees(radius / earthRadius)
val minLat = lat - toDegrees(radius / earthRadius)
val maxLon = lon + toDegrees(radius / earthRadius / cos(toRadians(lat)))
val minLon = lon - toDegrees(radius / earthRadius / cos(toRadians(lat)))
val maxLat = lat + toDegrees(radius / EARTH_RADIUS)
val minLat = lat - toDegrees(radius / EARTH_RADIUS)
val maxLon = lon + toDegrees(radius / EARTH_RADIUS / cos(toRadians(lat)))
val minLon = lon - toDegrees(radius / EARTH_RADIUS / cos(toRadians(lat)))
return "$minLat,$minLon,$maxLat,$maxLon"
}
@@ -229,21 +228,21 @@ object GeoUtils {
fun movePoint(
latitude: Double,
longitude: Double,
distanceMeters: Double = 7.0,
distanceMeters: Double = 8.0,
bearingDegrees: Double = 0.0
): org.maplibre.spatialk.geojson.Point {
val earthRadius = 6371000.0 // meters
val radius = EARTH_RADIUS * 1000 // meters
val bearingRadians = toRadians(bearingDegrees)
val lat1 = toRadians(latitude)
val lon1 = toRadians(longitude)
val lat2 = asin(
sin(lat1) * cos(distanceMeters / earthRadius) +
cos(lat1) * sin(distanceMeters / earthRadius) * cos(bearingRadians)
sin(lat1) * cos(distanceMeters / radius) +
cos(lat1) * sin(distanceMeters / radius) * cos(bearingRadians)
)
val lon2 = lon1 + atan2(
sin(bearingRadians) * sin(distanceMeters / earthRadius) * cos(lat1),
cos(distanceMeters / earthRadius) - sin(lat1) * sin(lat2)
sin(bearingRadians) * sin(distanceMeters / radius) * cos(lat1),
cos(distanceMeters / radius) - sin(lat1) * sin(lat2)
)
return org.maplibre.spatialk.geojson.Point(toDegrees(lon2), toDegrees(lat2))
}
@@ -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="M280,880L280,514Q229,500 194.5,458Q160,416 160,360L160,80L240,80L240,360L280,360L280,80L360,80L360,360L400,360L400,80L480,80L480,360Q480,416 445.5,458Q411,500 360,514L360,880L280,880ZM680,880L680,560L560,560L560,280Q560,197 618.5,138.5Q677,80 760,80L760,880L680,880Z"/>
</vector>
@@ -73,4 +73,5 @@
<string name="engine_type">Motortyp</string>
<string name="alternative_routes">Alternative Routen</string>
<string name="wait">Warten</string>
<string name="restaurant">Restaurant</string>
</resources>
@@ -57,4 +57,5 @@
<string name="engine_type">Τύπος κινητήρα</string>
<string name="alternative_routes">Εναλλακτικές διαδρομές</string>
<string name="wait">Περιμένετε</string>
<string name="restaurant">Restaurant</string>
</resources>
@@ -57,4 +57,5 @@
<string name="engine_type">Typ silnika</string>
<string name="alternative_routes">Alternatywne trasy</string>
<string name="wait">Czekaj</string>
<string name="restaurant">Restaurant</string>
</resources>
@@ -60,4 +60,5 @@
<string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
<string name="wait">Wait</string>
<string name="restaurant">Restaurant</string>
</resources>