This commit is contained in:
Dimitris
2025-12-16 07:19:29 +01:00
parent 72872cddeb
commit d546ede0e5
22 changed files with 589 additions and 137 deletions

View File

@@ -134,6 +134,13 @@ data class ValhallaLocation (
var language: String
)
data class BoundingBox (
var southernLat : Double,
var westernLon: Double,
var northerLat : Double,
var easternLon : Double
)
object Constants {
const val STYLE: String = "https://kouros-online.de/liberty.json"
@@ -141,12 +148,22 @@ object Constants {
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
const val TAG: String = "Navigation"
const val CATEGORIES: String = "Categories"
const val CONTACTS: String = "Contacts"
const val RECENT: String = "Recent"
const val FAVORITES: String = "Favorites"
const val GAS_STATION: String ="GasStation"
const val PHARMACY: String ="pharmacy"
const val CHARGING_STATION: String ="charging_station"
val categories = listOf("Tankstelle", "Apotheke", "Ladestationen")
/** The initial location to use as an anchor for searches. */
val homeLocation: Location = Location(LocationManager.GPS_PROVIDER)
val home2Location: Location = Location(LocationManager.GPS_PROVIDER)
@@ -176,7 +193,7 @@ object Constants {
const val MAXIMAL_ROUTE_DEVIATION = 100.0
const val DESTINATION_ARRIVAL_DISTANCE = 20.0
const val DESTINATION_ARRIVAL_DISTANCE = 40.0
}

View File

@@ -17,8 +17,8 @@
package com.kouros.navigation.data
import android.location.Location
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox
import org.json.JSONArray
import java.net.Authenticator
import java.net.HttpURLConnection
@@ -98,7 +98,6 @@ class NavigationRepository {
return places
}
private fun fetchUrl(url: String, authenticator : Boolean): String {
try {
if (authenticator) {

View File

@@ -0,0 +1,72 @@
package com.kouros.navigation.data
import android.location.Location
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.overpass.Amenity
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox2
import com.kouros.navigation.utils.NavigationUtils.getOverpassBbox
import hu.supercluster.overpasser.library.output.OutputFormat
import hu.supercluster.overpasser.library.output.OutputModificator
import hu.supercluster.overpasser.library.output.OutputOrder
import hu.supercluster.overpasser.library.output.OutputVerbosity
import hu.supercluster.overpasser.library.query.OverpassQuery
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
class Overpass {
fun getAmenities(category: String, location: Location) : List<Elements> {
val boundingBox = getOverpassBbox(location, 2.0)
val bb = getBoundingBox2(location, 2.0)
val url = "https://overpass.kumi.systems/api/interpreter"
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
"Accept",
"application/json"
)
httpURLConnection.setDoOutput(true);
val query = OverpassQuery()
.format(OutputFormat.JSON)
.timeout(30)
.filterQuery()
.node()
.amenity("charging_station")
.tagNot("access", "private")
.boundingBox(bb.southernLat, bb.westernLon, bb.northerLat, bb.easternLon)
.end()
.output(OutputVerbosity.BODY, OutputModificator.CENTER, OutputOrder.QT, 100)
.build()
// define a query
val test = """
|[out:json];
|(
| node[amenity=$category]
| ($boundingBox);
|);
|out body;
""".trimMargin()
// Send the JSON we created
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(test)
outputStreamWriter.flush()
// Check if the connection is successful
val responseCode = httpURLConnection.responseCode
println("Overpass: $responseCode")
if (responseCode == HttpURLConnection.HTTP_OK) {
val response = httpURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
println("Overpass: $response")
return overpass.elements
}
return emptyList()
}
}

View File

@@ -0,0 +1,13 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Amenity (
@SerializedName("version" ) var version : Double? = null,
@SerializedName("generator" ) var generator : String? = null,
@SerializedName("osm3s" ) var osm3s : Osm3s? = Osm3s(),
@SerializedName("elements" ) var elements : ArrayList<Elements> = arrayListOf()
)

View File

@@ -0,0 +1,14 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Elements (
@SerializedName("type" ) var type : String? = null,
@SerializedName("id" ) var id : Long? = null,
@SerializedName("lat" ) var lat : Double? = null,
@SerializedName("lon" ) var lon : Double? = null,
@SerializedName("tags" ) var tags : Tags? = Tags()
)

View File

@@ -0,0 +1,11 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Osm3s (
@SerializedName("timestamp_osm_base" ) var timestampOsmBase : String? = null,
@SerializedName("copyright" ) var copyright : String? = null
)

View File

@@ -0,0 +1,22 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Tags (
@SerializedName("amenity" ) var amenity : String? = null,
@SerializedName("authentication:none" ) var authenticationNone : String? = null,
@SerializedName("capacity" ) var capacity : String? = null,
@SerializedName("motorcar" ) var motorcar : String? = null,
@SerializedName("network" ) var network : String? = null,
@SerializedName("opening_hours" ) var openingHours : String? = null,
@SerializedName("operator" ) var operator : String? = null,
@SerializedName("operator:short" ) var operatorShort : String? = null,
@SerializedName("operator:wikidata" ) var operatorWikidata : String? = null,
@SerializedName("operator:wikipedia" ) var operatorWikipedia : String? = null,
@SerializedName("ref" ) var ref : String? = null,
@SerializedName("socket:type2" ) var socketType2 : String? = null,
@SerializedName("socket:type2:output" ) var socketType2Ooutput : String? = null
)

View File

@@ -22,7 +22,7 @@
"id": "background",
"type": "background",
"layout": {"visibility": "visible"},
"paint": {"background-color": "rgba(28, 28, 35, 1)"}
"paint": {"background-color": "rgba(39, 46, 57, 1)"}
},
{
"id": "natural_earth",
@@ -903,7 +903,11 @@
["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
["match", ["get", "class"], ["service", "track"], true, false]
],
"layout": {"line-cap": "round", "line-join": "round"},
"layout": {
"line-cap": "round",
"line-join": "round",
"visibility": "visible"
},
"paint": {
"line-width": [
"interpolate",
@@ -916,7 +920,7 @@
20,
11
],
"line-color": "rgba(65, 74, 92, 1)"
"line-color": "rgba(77, 94, 123, 1)"
}
},
{
@@ -1005,7 +1009,7 @@
],
"layout": {"line-cap": "round", "line-join": "round"},
"paint": {
"line-color": "#e9ac77",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1029,7 +1033,7 @@
],
"layout": {"line-join": "round"},
"paint": {
"line-color": "#e9ac77",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1059,7 +1063,7 @@
],
"layout": {"line-cap": "round", "line-join": "round"},
"paint": {
"line-color": "#e9ac77",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1245,7 +1249,7 @@
],
"layout": {"line-cap": "round", "line-join": "round"},
"paint": {
"line-color": "rgba(105, 170, 87, 1)",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1271,7 +1275,7 @@
],
"layout": {"line-join": "round"},
"paint": {
"line-color": "#fea",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1306,7 +1310,7 @@
5,
"hsl(26,87%,62%)",
6,
"#fc8"
"#ab9"
],
"line-width": [
"interpolate",
@@ -1847,7 +1851,7 @@
],
"layout": {"line-join": "round"},
"paint": {
"line-color": "#fea",
"line-color": "rgba(65, 74, 92, 34)",
"line-width": [
"interpolate",
["exponential", 1.2],

View File

@@ -4,6 +4,8 @@ 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
@@ -35,6 +37,7 @@ open class RouteModel() {
set(value) {
routeState = routeState.copy(route = value)
}
fun startNavigation(routeString: String) {
val newRoute = Route.Builder()
.route(routeString)
@@ -49,7 +52,7 @@ open class RouteModel() {
this.routeState = routeState.copy(
route = null,
isNavigating = false,
// destination = Place(),
// destination = Place(),
arrived = false,
maneuverType = 0,
currentShapeIndex = 0,
@@ -100,30 +103,31 @@ open class RouteModel() {
}
fun currentStep(): StepData {
val currentManeuver = route.currentManeuver()
// Determine if we should display the current or the next maneuver
val distanceToNextStep = leftStepDistance()
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
val shouldAdvance =
isNearNextManeuver && route.currentManeuverIndex < (route.maneuvers.size - 1)
isNearNextManeuver && route.currentManeuverIndex < (route.maneuvers.size)
// Determine the maneuver type and corresponding icon
var maneuverType = if (hasArrived(currentManeuver.type)) {
currentManeuver.type
} else {
ManeuverType.None.value
}
// Get the single, correct maneuver for this state
val relevantManeuver = if (shouldAdvance) {
route.nextManeuver() // This advances the route's state
} else {
route.currentManeuver()
}
// Safely get the street name from the maneuver
val streetName = relevantManeuver.streetNames?.firstOrNull() ?: ""
// Determine the maneuver type and corresponding icon
val maneuverType = if (hasArrived(relevantManeuver.type)) {
relevantManeuver.type
} else {
ManeuverType.None.value
if (shouldAdvance) {
maneuverType = relevantManeuver.type
}
val maneuverIconPair = maneuverIcon(maneuverType)
// Construct and return the final StepData object
return StepData(
streetName,
@@ -135,6 +139,40 @@ open class RouteModel() {
)
}
fun currentStepOld(): StepData {
val maneuver = route.currentManeuver()
var text = ""
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
val distanceStepLeft = leftStepDistance()
when (distanceStepLeft) {
in 0.0..Constants.NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
}
}
}
val type = if (hasArrived(maneuver.type)) {
maneuver.type
} else {
ManeuverType.None.value
}
var routing: (Pair<Int, Int>) = maneuverIcon(type)
when (distanceStepLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
routing = maneuverIcon(maneuverType)
}
}
}
return StepData(text, distanceStepLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance())
}
fun nextStep(): StepData {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
@@ -290,12 +328,10 @@ open class RouteModel() {
return routeState.isNavigating
}
fun isArrived(): Boolean {
return routeState.arrived
}
fun hasArrived(type: Int): Boolean {
return type == ManeuverType.DestinationRight.value
// return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value
return type == ManeuverType.DestinationRight.value
|| routeState.maneuverType == ManeuverType.Destination.value
|| routeState.maneuverType == ManeuverType.DestinationLeft.value
}

View File

@@ -13,11 +13,13 @@ import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Locations
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Overpass
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor
@@ -55,6 +57,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData<List<Place>>()
}
val elements: MutableLiveData<List<Elements>> by lazy {
MutableLiveData<List<Elements>>()
}
fun loadRecentPlace(location: Location) {
viewModelScope.launch(Dispatchers.IO) {
@@ -113,6 +118,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
.orderDesc(Place_.lastDate)
.build()
val results = query.find()
println("Favorites $results")
query.close()
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
@@ -231,6 +237,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
return place.address.road
}
fun getAmenities(category: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities(category, location)
elements.postValue(amenities)
}
}
fun saveFavorite(place: Place) {
place.category = Constants.FAVORITES
savePlace(place)
@@ -258,7 +271,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val current = LocalDateTime.now(ZoneOffset.UTC)
place.lastDate = current.atZone(ZoneOffset.UTC).toEpochSecond()
placeBox.put(place)
println("Save Recent $place")
println("Save Place $place")
} catch (e: Exception) {
e.printStackTrace()
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.location.Location
import android.location.LocationManager
import androidx.core.content.edit
import com.kouros.navigation.data.BoundingBox
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection
@@ -158,6 +159,23 @@ object NavigationUtils {
return jsonString
}
fun getOverpassBbox(location: Location, radius: Double): String {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return "$swLon,$swLat,$neLon,$neLat"
}
fun getBoundingBox2(location: Location, radius: Double): BoundingBox {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return BoundingBox(swLat ?: 0.0 , swLon ?: 0.0, neLat ?: 0.0, neLon ?: 0.0)
}
fun getBoundingBox(
lat: Double,
lon: Double,
@@ -176,30 +194,6 @@ object NavigationUtils {
"se" to mapOf("lat" to minLat, "lon" to maxLon)
)
}
fun computeOffset(from: Location, distance: Double, heading: Double): Location {
val earthRadius = 6371009.0
var distance = distance
var heading = heading
distance /= earthRadius
heading = toRadians(heading)
val fromLat: Double = toRadians(from.latitude)
val fromLng: Double = toRadians(from.longitude)
val cosDistance: Double = cos(distance)
val sinDistance = sin(distance)
val sinFromLat = sin(fromLat)
val cosFromLat: Double = cos(fromLat)
val sinLat: Double = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading)
val dLng: Double = atan2(
sinDistance * cosFromLat * sin(heading),
cosDistance - sinFromLat * sinLat
)
val snap = Location(LocationManager.GPS_PROVIDER)
snap.latitude = toDegrees(asin(sinLat))
snap.longitude = toDegrees(fromLng + dLng)
return snap
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
}
}
fun calculateZoom(speed: Double?): Double {
@@ -208,11 +202,12 @@ fun calculateZoom(speed: Double?): Double {
}
val speedKmh = (speed * 3.6).toInt()
val zoom = when (speedKmh) {
in 0..10 -> 17.0
in 11..30 -> 16.0
in 31..50 -> 16.0
in 0..10 -> 18.0
in 11..30 -> 17.0
in 21..40 -> 16.0
in 31..50 -> 15.0
in 51..60 -> 15.0
else -> 15
else -> 14
}
return zoom.toDouble()
}