Overpass
This commit is contained in:
@@ -4,7 +4,7 @@ import androidx.compose.ui.graphics.Color
|
||||
|
||||
val NavigationColorLight = Color(0xFF17A119)
|
||||
|
||||
val NavigationColorDark = Color(0xFF4EDE10)
|
||||
val NavigationColorDark = Color(0xFF03411F)
|
||||
|
||||
val RouteColor = Color(0xFF195D02)
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ data class StepData (
|
||||
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList(), 0, 0)),
|
||||
var exitNumber: Int = 0,
|
||||
var message: String = "",
|
||||
var roadNumbers: List<String> = emptyList(),
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -123,7 +125,11 @@ object Constants {
|
||||
/** The initial location to use as an anchor for searches. */
|
||||
val homeVogelhart = location(11.5793748, 48.185749)
|
||||
val homeHohenwaldeck = location( 11.594322, 48.1164817)
|
||||
val ioannina = location( 20.826237, 39.690174)
|
||||
val subislawa = location(18.570808, 54.420647)
|
||||
val a94 = location(11.872097,48.163449)
|
||||
|
||||
val a9 = location(11.621556, 48.204402,)
|
||||
const val NEXT_STEP_THRESHOLD = 500.0
|
||||
|
||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||
@@ -138,8 +144,12 @@ object Constants {
|
||||
|
||||
const val TRAFFIC_UPDATE = 300
|
||||
|
||||
const val SPEED_UPDATE_DISTANCE = 600F
|
||||
|
||||
const val INSTRUCTION_DISTANCE = 50
|
||||
|
||||
const val SPEED_BEARING_DEVIATION = 60
|
||||
|
||||
const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED"
|
||||
|
||||
const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED"
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
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()
|
||||
|
||||
data class Amenity(
|
||||
val elements: List<Elements>,
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kouros.navigation.data.overpass
|
||||
|
||||
data class Bounds(
|
||||
val maxlat: Double,
|
||||
val maxlon: Double,
|
||||
val minlat: Double,
|
||||
val minlon: Double
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kouros.navigation.data.overpass
|
||||
|
||||
data class ElementSearch(
|
||||
val element: Elements,
|
||||
val distance: Double,
|
||||
val bearing: Float,
|
||||
)
|
||||
@@ -1,15 +1,14 @@
|
||||
package com.kouros.navigation.data.overpass
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Elements (
|
||||
|
||||
@SerializedName("type" ) var type : String = "",
|
||||
@SerializedName("id" ) var id : Long = 0,
|
||||
@SerializedName("lat" ) var lat : Double = 0.0,
|
||||
@SerializedName("lon" ) var lon : Double = 0.0,
|
||||
@SerializedName("tags" ) var tags : Tags = Tags(),
|
||||
var distance : Double = 0.0
|
||||
data class Elements(
|
||||
val bounds: Bounds,
|
||||
val geometry: List<Geometry>,
|
||||
val id: Long = 0,
|
||||
val lat: Double= 0.0,
|
||||
val lon: Double = 0.0,
|
||||
val tags: Tags,
|
||||
val type: String = "",
|
||||
var distance : Double = 0.0
|
||||
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kouros.navigation.data.overpass
|
||||
|
||||
data class Geometry(
|
||||
val lat: Double = 0.0,
|
||||
val lon: Double = 0.0
|
||||
)
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.kouros.navigation.data.overpass
|
||||
|
||||
import android.location.Location
|
||||
import android.util.Log
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.data.BuildConfig
|
||||
import com.kouros.navigation.utils.GeoUtils.getBoundingBox
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
@@ -9,12 +11,43 @@ import java.net.URL
|
||||
|
||||
class Overpass {
|
||||
|
||||
//val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
|
||||
//val overpassUrl = "https://overpass-api.de/api"
|
||||
val overpassUrl = "https://kouros-online.de/overpass/interpreter"
|
||||
var overpassUrl = if (BuildConfig.DEBUG)
|
||||
"http://192.168.1.37/api/interpreter"
|
||||
else
|
||||
"https://kouros-online.de/api/interpreter"
|
||||
|
||||
|
||||
fun getAround(radius: Int, linestring: String): List<Elements> {
|
||||
fun getSpeedLimit(radius: Float, linestring: String, street: String, roadNumbers: List<String>): List<Elements> {
|
||||
val streetName = if (street.length > 10) {
|
||||
street.substring(0, 10)
|
||||
} else {
|
||||
street
|
||||
}
|
||||
val name = if (streetName.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
"[name~\"^${streetName}\"]"
|
||||
}
|
||||
|
||||
val regex = Regex("""\d+|\D+""")
|
||||
val search = "way[maxspeed](around:$radius,$linestring)$name[!destination][highway!=\"motorway_link\"]"
|
||||
var waySearch = search
|
||||
for ((index, r) in roadNumbers.withIndex()) {
|
||||
val result = regex.findAll(r).map { it.groupValues.first() }.toList()
|
||||
if (index > 0) {
|
||||
waySearch = waySearch.plus(";").plus(search)
|
||||
}
|
||||
var refValue = result.first().trim().plus(" ")
|
||||
for (res in result.subList(1, result.size)) {
|
||||
refValue = refValue.plus(res)
|
||||
}
|
||||
// International reference starts with "E"
|
||||
waySearch = if (r.first().toString() == "E") {
|
||||
waySearch.plus("[int_ref~\"${refValue}\"]")
|
||||
} else {
|
||||
waySearch.plus("[ref~\"${refValue}\"]")
|
||||
}
|
||||
}
|
||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
||||
httpURLConnection.requestMethod = "POST"
|
||||
httpURLConnection.setRequestProperty(
|
||||
@@ -26,15 +59,14 @@ class Overpass {
|
||||
val searchQuery = """
|
||||
|[out:json];
|
||||
|(
|
||||
| way[highway](around:$radius,$linestring)
|
||||
| ;
|
||||
| $waySearch;
|
||||
|);
|
||||
|out body;
|
||||
|out body geom;
|
||||
""".trimMargin()
|
||||
//println("way[highway](around:$radius,$linestring)")
|
||||
return overpassApi(httpURLConnection, searchQuery)
|
||||
}
|
||||
|
||||
|
||||
fun getAmenities(
|
||||
type: String,
|
||||
category: String,
|
||||
@@ -59,7 +91,7 @@ class Overpass {
|
||||
| ($boundingBox);
|
||||
|);
|
||||
|(._;>;);
|
||||
|out body;
|
||||
|out body geom;
|
||||
""".trimMargin()
|
||||
return overpassApi(httpURLConnection, searchQuery)
|
||||
}
|
||||
@@ -70,17 +102,22 @@ class Overpass {
|
||||
outputStreamWriter.write(searchQuery)
|
||||
outputStreamWriter.flush()
|
||||
// Check if the connection is successful
|
||||
httpURLConnection.requestMethod = "POST"
|
||||
val responseCode = httpURLConnection.responseCode
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
val response = httpURLConnection.inputStream.bufferedReader()
|
||||
.use { it.readText() } // defaults to UTF-8
|
||||
if (response.startsWith("<?xml")) {
|
||||
return emptyList()
|
||||
}
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
val overpass = gson.fromJson(response, Amenity::class.java)
|
||||
return overpass.elements
|
||||
|
||||
} else {
|
||||
Log.e("OverpassApi", responseCode.toString())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Speed $e")
|
||||
Log.e("OverpassApi", e.toString())
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@@ -4,21 +4,28 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Tags(
|
||||
@SerializedName("name") var name: String? = null,
|
||||
@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 socketType2Output: String? = null,
|
||||
@SerializedName("maxspeed") var maxspeed: String = "0",
|
||||
@SerializedName("direction") var direction: String? = null,
|
||||
|
||||
val destination: String = "",
|
||||
val highway: String = "",
|
||||
val lanes: String = "",
|
||||
val lit: String = "",
|
||||
val maxspeed: String = "0",
|
||||
val name: String = "",
|
||||
val oneway: String = "",
|
||||
val ref: String = "",
|
||||
@SerializedName("int_ref") val intRef: String = "",
|
||||
val sidewalk: String = "",
|
||||
val smoothness: String = "",
|
||||
val surface: String = "",
|
||||
val amenity: String = "",
|
||||
val capacity: String = "",
|
||||
val motorcar: String = "",
|
||||
val network: String = "",
|
||||
val openingHours: String = "",
|
||||
val operator: String = "",
|
||||
val operatorShort: String = "",
|
||||
val operatorWikidata: String = "",
|
||||
val operatorWikipedia: String = "",
|
||||
val socketType2: String = "",
|
||||
val socketType2Output: String = "",
|
||||
val direction: String = "",
|
||||
)
|
||||
@@ -12,5 +12,6 @@ data class Step(
|
||||
val distance: Double = 0.0,
|
||||
val street : String = "",
|
||||
val intersection: List<Intersection> = mutableListOf(),
|
||||
val countryCode : String = ""
|
||||
val countryCode : String = "",
|
||||
val roadNumbers : List<String> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
|
||||
private const val tomtomFields =
|
||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||
|
||||
val useLocal = false // BuildConfig.DEBUG
|
||||
val useLocal = BuildConfig.DEBUG
|
||||
|
||||
val useLocalTraffic = BuildConfig.DEBUG
|
||||
|
||||
@@ -40,7 +40,7 @@ class TomTomRepository : NavigationRepository() {
|
||||
}
|
||||
if (useLocal) {
|
||||
return fetchUrl(
|
||||
"https://kouros-online.de/tomtom_routing.json",
|
||||
"http://192.168.1.37/tomtom_routing.json",
|
||||
false
|
||||
)
|
||||
}
|
||||
@@ -104,7 +104,7 @@ class TomTomRepository : NavigationRepository() {
|
||||
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
|
||||
return if (useLocalTraffic) {
|
||||
fetchUrl(
|
||||
"https://kouros-online.de/tomtom_traffic.json",
|
||||
"http://192.168.1.37/tomtom_traffic.json",
|
||||
false
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -99,6 +99,11 @@ class TomTomRoute {
|
||||
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
||||
stepDuration =
|
||||
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
|
||||
val roadNumbers = if (lastInstruction.roadNumbers != null) {
|
||||
lastInstruction.roadNumbers
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
street = street,
|
||||
@@ -106,7 +111,8 @@ class TomTomRoute {
|
||||
duration = stepDuration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections,
|
||||
countryCode = lastInstruction.countryCode
|
||||
countryCode = lastInstruction.countryCode,
|
||||
roadNumbers = roadNumbers
|
||||
)
|
||||
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
|
||||
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.kouros.navigation.model
|
||||
|
||||
//import com.kouros.navigation.data.Preferences.boxStore
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.util.Log
|
||||
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@@ -12,15 +11,18 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.SPEED_BEARING_DEVIATION
|
||||
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
|
||||
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.nominatim.Search
|
||||
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.utils.Levenshtein
|
||||
import com.kouros.navigation.utils.countryCodeSpeedLimit
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -31,9 +33,12 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.maplibre.geojson.FeatureCollection
|
||||
import java.lang.reflect.Modifier
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.collections.first
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.comparisons.compareBy
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/**
|
||||
* ViewModel for navigation-related data operations.
|
||||
@@ -86,6 +91,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing POI elements from Overpass API */
|
||||
val speedElements = mutableListOf<Elements>()
|
||||
|
||||
|
||||
/** LiveData containing speed camera locations */
|
||||
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
||||
MutableLiveData()
|
||||
@@ -107,10 +116,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
|
||||
val gson: com.google.gson.Gson = GsonBuilder().create()
|
||||
|
||||
/**
|
||||
* Retrieves recent places from Preferences as a Flow.
|
||||
*/
|
||||
fun recentPlacesFlow(context: Context, location: Location,): Flow<Place> = callbackFlow {
|
||||
fun recentPlacesFlow(context: Context, location: Location): Flow<Place> = callbackFlow {
|
||||
for (place in recentPlaces.value!!) {
|
||||
trySend(place)
|
||||
}
|
||||
@@ -399,24 +409,93 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||
* Posts speed limit to maxSpeed LiveData.
|
||||
*/
|
||||
fun getMaxSpeed(location: Location, street: String) {
|
||||
fun getSpeedLimit(
|
||||
location: Location,
|
||||
routeBearing: Float,
|
||||
countryCode: String,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val levenshtein = Levenshtein()
|
||||
val lineString = "${location.latitude},${location.longitude}"
|
||||
val amenities = Overpass().getAround(10, lineString)
|
||||
amenities.forEach {
|
||||
if (it.tags.name != null) {
|
||||
val distance =
|
||||
levenshtein.distance(it.tags.name!!, street)
|
||||
if (distance < 5) {
|
||||
val speed = it.tags.maxspeed.toInt()
|
||||
maxSpeed.postValue(speed)
|
||||
}
|
||||
}
|
||||
}
|
||||
maxSpeed.postValue(calculateSpeedLimit(location, routeBearing, countryCode))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||
*/
|
||||
fun calculateSpeedLimit(location: Location, routeBearing: Float, countryCode: String): Int {
|
||||
var speed = 0
|
||||
var element: Elements?
|
||||
val search = mutableListOf<ElementSearch>()
|
||||
|
||||
speedElements.filter { it.type == "way"}.forEach {
|
||||
var streetBearingSum = 0F
|
||||
var streetBearingAvg = 0F
|
||||
var distance = 0F
|
||||
var maxDistance = 1000F
|
||||
for ((geoIndex, geo) in it.geometry.withIndex()) {
|
||||
val geometryLocation = location(geo.lon, geo.lat)
|
||||
distance = geometryLocation.distanceTo(location)
|
||||
if (distance < maxDistance) {
|
||||
maxDistance = distance
|
||||
}
|
||||
if (geoIndex > 0) {
|
||||
val prevLocation =
|
||||
location(it.geometry[geoIndex - 1].lon, it.geometry[geoIndex - 1].lat)
|
||||
val streetBearing = prevLocation.bearingTo(geometryLocation).absoluteValue
|
||||
streetBearingSum = (streetBearingSum + streetBearing)
|
||||
streetBearingAvg = streetBearingSum / geoIndex
|
||||
}
|
||||
}
|
||||
val bearing = calculateBearing(it, streetBearingAvg, routeBearing)
|
||||
if (bearing < SPEED_BEARING_DEVIATION) {
|
||||
search.add(
|
||||
ElementSearch(
|
||||
it,
|
||||
maxDistance.toDouble(),
|
||||
streetBearingAvg.absoluteValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val result = search.sortedWith(compareBy<ElementSearch> { it.distance }.thenByDescending { it.bearing })
|
||||
if (result.isNotEmpty()) {
|
||||
element = result.first().element
|
||||
//Log.d("NavigationViewModel", "Distance: ${result.first().distance} Bearing: ${result.first().bearing} RouteBearing $routeBearing")
|
||||
speed = if (element.tags.maxspeed == "none" && element.tags.highway == "motorway") {
|
||||
countryCodeSpeedLimit(countryCode)
|
||||
} else {
|
||||
element.tags.maxspeed.toInt()
|
||||
}
|
||||
}
|
||||
return speed
|
||||
}
|
||||
|
||||
private fun calculateBearing(element: Elements, streetBearingAvg : Float, routeBearing: Float) : Float {
|
||||
return if (element.tags.oneway.isNotEmpty()) {
|
||||
(streetBearingAvg - routeBearing.absoluteValue).absoluteValue
|
||||
} else {
|
||||
0F
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for speed limit on current road.
|
||||
* Posts speed elements to speedElements.
|
||||
*/
|
||||
fun updateSpeedLimit(
|
||||
location: Location,
|
||||
street: String,
|
||||
roadNumbers: List<String>
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val lineString = "${location.latitude},${location.longitude}"
|
||||
val elements = Overpass().getSpeedLimit(SPEED_UPDATE_DISTANCE, lineString, street, roadNumbers)
|
||||
speedElements.clear()
|
||||
speedElements.addAll(elements)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves a place as a favorite in Preferences.
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.kouros.navigation.model
|
||||
|
||||
import android.location.Location
|
||||
import android.util.Log
|
||||
import androidx.car.app.navigation.model.Step
|
||||
import com.kouros.navigation.data.Constants.MAXIMUM_LOCATION_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.NEAREST_LOCATION_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
|
||||
import com.kouros.navigation.utils.location
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class RouteCalculator(var routeModel: RouteModel) {
|
||||
|
||||
@@ -107,14 +106,27 @@ class RouteCalculator(var routeModel: RouteModel) {
|
||||
return nowUtcMillis + timeToDestinationMillis
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the speed limit in the view model.
|
||||
*/
|
||||
fun updateSpeedLimit(location: Location, viewModel: NavigationViewModel) {
|
||||
if (routeModel.isNavigating()) {
|
||||
// speed limit
|
||||
val distance = lastSpeedLocation.distanceTo(location)
|
||||
if (distance > 500 || lastSpeedIndex < routeModel.route.currentStepIndex) {
|
||||
if ((distance > SPEED_UPDATE_DISTANCE * 2) || lastSpeedIndex < routeModel.route.currentStepIndex) {
|
||||
lastSpeedIndex = routeModel.route.currentStepIndex
|
||||
lastSpeedLocation = location
|
||||
viewModel.getMaxSpeed(location, routeModel.route.currentStep().street)
|
||||
viewModel.updateSpeedLimit(
|
||||
location,
|
||||
routeModel.route.currentStep().street,
|
||||
routeModel.currentStep().roadNumbers
|
||||
)
|
||||
} else {
|
||||
viewModel.getSpeedLimit(
|
||||
location,
|
||||
routeModel.navState.routeBearing,
|
||||
routeModel.currentStep.countryCode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,8 @@ open class RouteModel {
|
||||
leftDistance = routeCalculator.travelLeftDistance(),
|
||||
lane = currentLanes,
|
||||
exitNumber = exitNumber,
|
||||
message = currentStep.maneuver.message
|
||||
message = currentStep.maneuver.message,
|
||||
roadNumbers = currentStep.roadNumbers
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.location.Location
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import org.maplibre.geojson.FeatureCollection
|
||||
import org.maplibre.geojson.LineString
|
||||
import org.maplibre.geojson.Point
|
||||
import org.maplibre.spatialk.geojson.Feature
|
||||
import org.maplibre.spatialk.geojson.dsl.addFeature
|
||||
@@ -19,7 +20,7 @@ import kotlin.math.pow
|
||||
|
||||
object GeoUtils {
|
||||
|
||||
fun snapLocation(location: Location, stepCoordinates: List<Point>) : Location {
|
||||
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
|
||||
val newLocation = Location(location)
|
||||
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
||||
if (stepCoordinates.size > 1) {
|
||||
@@ -34,7 +35,7 @@ object GeoUtils {
|
||||
return newLocation
|
||||
}
|
||||
|
||||
fun decodePolyline(encoded: String, precision: Int = 6): List<List<Double>> {
|
||||
fun decodePolyline(encoded: String, precision: Int = 6): List<List<Double>> {
|
||||
val factor = 10.0.pow(precision)
|
||||
var lat = 0
|
||||
var lng = 0
|
||||
@@ -91,18 +92,20 @@ object GeoUtils {
|
||||
}
|
||||
|
||||
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
|
||||
// return createPointCollection(lineCoordinates, "Route")
|
||||
// return createPointCollection(lineCoordinates, "Route")
|
||||
val lineString = buildLineString {
|
||||
lineCoordinates.forEach {
|
||||
add(org.maplibre.spatialk.geojson.Point(
|
||||
it[0],
|
||||
it[1]
|
||||
))
|
||||
add(
|
||||
org.maplibre.spatialk.geojson.Point(
|
||||
it[0],
|
||||
it[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val feature = Feature(lineString, null)
|
||||
val featureCollection = org.maplibre.spatialk.geojson.FeatureCollection(feature)
|
||||
return featureCollection.toJson()
|
||||
return featureCollection.toJson()
|
||||
}
|
||||
|
||||
fun createPointCollection(lineCoordinates: List<List<Double>>, category: String): String {
|
||||
@@ -114,9 +117,30 @@ object GeoUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
return featureCollection.toJson()
|
||||
return featureCollection.toJson()
|
||||
}
|
||||
|
||||
fun createStartCollection(geoJson: String): String {
|
||||
val featureCollection = FeatureCollection.fromJson(geoJson)
|
||||
val geometry = featureCollection.features()!!.first().geometry()
|
||||
val coordinates = (geometry as LineString)
|
||||
val first = coordinates.coordinates().first()
|
||||
val points = createPointCollection(
|
||||
listOf(listOf(first.coordinates()[0], first.coordinates()[1])), "End"
|
||||
)
|
||||
return points
|
||||
}
|
||||
|
||||
fun createEndCollection(geoJson: String): String {
|
||||
val featureCollection = FeatureCollection.fromJson(geoJson)
|
||||
val geometry = featureCollection.features()!!.first().geometry()
|
||||
val coordinates = (geometry as LineString)
|
||||
val last = coordinates.coordinates().last()
|
||||
val points = createPointCollection(
|
||||
listOf(listOf(last.coordinates()[0], last.coordinates()[1])), "End"
|
||||
)
|
||||
return points
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the lat and len of a square around a point.
|
||||
@@ -131,6 +155,7 @@ object GeoUtils {
|
||||
|
||||
return "$lngMin,$latMin,$lngMax,$latMax"
|
||||
}
|
||||
|
||||
fun getBoundingBox(
|
||||
lat: Double,
|
||||
lon: Double,
|
||||
@@ -144,4 +169,18 @@ object GeoUtils {
|
||||
|
||||
return "$minLat,$minLon,$maxLat,$maxLon"
|
||||
}
|
||||
|
||||
fun isLocationInBoundingBox(
|
||||
bottomLeftLat: Double,
|
||||
bottomLeftLon: Double,
|
||||
topRightLat: Double,
|
||||
topRightLon: Double,
|
||||
location: Location
|
||||
): Boolean {
|
||||
val isInside =
|
||||
location.latitude in bottomLeftLat..topRightLat
|
||||
&& location.longitude >= bottomLeftLon
|
||||
&& location.longitude <= topRightLon
|
||||
return isInside
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,11 @@ class Levenshtein {
|
||||
* @param limit the maximum result to compute before stopping, terminating calculation early.
|
||||
* @return the computed Levenshtein distance.
|
||||
*/
|
||||
fun distance(first: CharSequence, second: CharSequence, limit: Int = Int.MAX_VALUE): Int {
|
||||
fun distance(first: CharSequence, second: CharSequence, countryCode: String, limit: Int = Int.MAX_VALUE): Int {
|
||||
if (countryCode == "GRC") return 0
|
||||
if (first == second) return 0
|
||||
if (first.isEmpty()) return second.length
|
||||
if (second.isEmpty()) return first.length
|
||||
if (first.isEmpty()) return 0
|
||||
if (second.isEmpty()) return 0
|
||||
|
||||
// initial costs is the edit distance from an empty string, which corresponds to the characters to inserts.
|
||||
// the array size is : length + 1 (empty string)
|
||||
|
||||
@@ -3,11 +3,10 @@ package com.kouros.navigation.utils
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.util.Log
|
||||
import androidx.car.app.model.Distance
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.Constants.TILT
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
@@ -94,15 +93,19 @@ fun calculateZoomFromBoundingBox(centerLocation: Location, previewDistance: Doub
|
||||
}
|
||||
|
||||
|
||||
fun calculateTilt(newZoom: Double, tilt: Double): Double =
|
||||
if (newZoom < 13) {
|
||||
0.0
|
||||
} else {
|
||||
if (tilt == 0.0) {
|
||||
TILT
|
||||
fun calculateTilt(viewStyle: ViewStyle, newZoom: Double, tilt: Double): Double =
|
||||
if (viewStyle == ViewStyle.VIEW) {
|
||||
if (newZoom < 13) {
|
||||
0.0
|
||||
} else {
|
||||
tilt
|
||||
if (tilt == 0.0) {
|
||||
TILT
|
||||
} else {
|
||||
tilt
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double): Double {
|
||||
@@ -134,13 +137,14 @@ fun Double.round(numFractionDigits: Int): Double {
|
||||
}
|
||||
|
||||
fun duration(
|
||||
preview: Boolean,
|
||||
viewStyle: ViewStyle,
|
||||
bearing: Double,
|
||||
lastBearing: Double,
|
||||
lastLocationUpdate: LocalDateTime
|
||||
): Duration {
|
||||
if (preview) {
|
||||
return 10.milliseconds
|
||||
if (viewStyle == ViewStyle.PREVIEW ||
|
||||
viewStyle == ViewStyle.PAN_VIEW) {
|
||||
return 100.milliseconds
|
||||
}
|
||||
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||
2.seconds
|
||||
@@ -184,3 +188,11 @@ fun formattedDistance(distanceMode: Int, distance: Double): Pair<Double, Int> {
|
||||
}
|
||||
return Pair(currentDistance, displayUnit)
|
||||
}
|
||||
|
||||
fun countryCodeSpeedLimit(countryCode: String) : Int {
|
||||
return when (countryCode) {
|
||||
"DEU", "FRA", "AUT", "GRE", "NLD", "ITA", "SLO", "SVK", "CZE" -> 130
|
||||
"POL", "BEL", "ESP", "PRT", "BGR", "HUN", "FIN" -> 120
|
||||
else -> 100
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="M390,220L450,220L450,160L390,160L390,220ZM510,220L510,160L570,160L570,220L510,220ZM390,460L390,400L450,400L450,460L390,460ZM630,340L630,280L690,280L690,340L630,340ZM630,460L630,400L690,400L690,460L630,460ZM510,460L510,400L570,400L570,460L510,460ZM630,220L630,160L690,160L690,220L630,220ZM450,280L450,220L510,220L510,280L450,280ZM270,800L270,160L330,160L330,220L390,220L390,280L330,280L330,340L390,340L390,400L330,400L330,800L270,800ZM570,400L570,340L630,340L630,400L570,400ZM450,400L450,340L510,340L510,400L450,400ZM390,340L390,280L450,280L450,340L390,340ZM510,340L510,280L570,280L570,340L510,340ZM570,280L570,220L630,220L630,280L570,280Z"/>
|
||||
</vector>
|
||||
@@ -72,4 +72,5 @@
|
||||
<string name="electric">Electric</string>
|
||||
<string name="engine_type">Engine type</string>
|
||||
<string name="alternative_routes">Alternative routes</string>
|
||||
<string name="wait">Wait</string>
|
||||
</resources>
|
||||
|
||||
@@ -56,4 +56,5 @@
|
||||
<string name="electric">Electric</string>
|
||||
<string name="engine_type">Engine type</string>
|
||||
<string name="alternative_routes">Alternative routes</string>
|
||||
<string name="wait">Wait</string>
|
||||
</resources>
|
||||
|
||||
@@ -56,4 +56,5 @@
|
||||
<string name="electric">Electric</string>
|
||||
<string name="engine_type">Engine type</string>
|
||||
<string name="alternative_routes">Alternative routes</string>
|
||||
<string name="wait">Wait</string>
|
||||
</resources>
|
||||
|
||||
@@ -59,4 +59,5 @@
|
||||
<string name="electric">Electric</string>
|
||||
<string name="engine_type">Engine type</string>
|
||||
<string name="alternative_routes">Alternative routes</string>
|
||||
<string name="wait">Wait</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user