Before TomTom Routing
This commit is contained in:
@@ -68,6 +68,7 @@ data class StepData (
|
||||
var leftDistance: Double,
|
||||
|
||||
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
|
||||
var exitNumber: Int = 0,
|
||||
)
|
||||
|
||||
|
||||
@@ -150,5 +151,5 @@ object Constants {
|
||||
|
||||
|
||||
enum class RouteEngine {
|
||||
VALHALLA, OSRM, GRAPHHOPPER
|
||||
VALHALLA, OSRM, TOMTOM, GRAPHHOPPER
|
||||
}
|
||||
|
||||
@@ -18,16 +18,13 @@ package com.kouros.navigation.data
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||
import com.kouros.navigation.utils.GeoUtils.getBoundingBox
|
||||
import org.json.JSONArray
|
||||
import java.net.Authenticator
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.PasswordAuthentication
|
||||
import java.net.URL
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
|
||||
abstract class NavigationRepository {
|
||||
@@ -36,27 +33,69 @@ abstract class NavigationRepository {
|
||||
|
||||
private val nominatimUrl = "https://kouros-online.de/nominatim/"
|
||||
|
||||
private val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
|
||||
abstract fun getRoute(currentLocation: Location, location: Location, carOrientation: Float, searchFilter: SearchFilter): String
|
||||
private val tomtomUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails"
|
||||
|
||||
fun getRouteDistance(currentLocation: Location, location: Location, carOrientation : Float, searchFilter: SearchFilter, context: Context): Double {
|
||||
val route = getRoute(currentLocation, location, carOrientation, searchFilter)
|
||||
private val tomtomFields =
|
||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||
|
||||
abstract fun getRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
carOrientation: Float,
|
||||
searchFilter: SearchFilter
|
||||
): String
|
||||
|
||||
fun getRouteDistance(
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
carOrientation: Float,
|
||||
searchFilter: SearchFilter,
|
||||
context: Context
|
||||
): Double {
|
||||
val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
val routeModel = RouteModel()
|
||||
routeModel.startNavigation(route, context)
|
||||
return routeModel.curRoute.summary.distance
|
||||
}
|
||||
|
||||
fun searchPlaces(search: String, location: Location) : String {
|
||||
val box = calculateSquareRadius(location.latitude, location.longitude, 20.0)
|
||||
val viewbox = "&bounded=1&viewbox=${box[2]},${box[0]},${box[3]},${box[1]}"
|
||||
return fetchUrl("${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true$viewbox", false)
|
||||
fun searchPlaces(search: String, location: Location): String {
|
||||
val box = calculateSquareRadius(location.latitude, location.longitude, 20.0)
|
||||
val viewbox = "&bounded=1&viewbox=${box}"
|
||||
return fetchUrl(
|
||||
"${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true$viewbox",
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
fun reverseAddress(location: Location) : String {
|
||||
return fetchUrl("${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true", false)
|
||||
fun reverseAddress(location: Location): String {
|
||||
return fetchUrl(
|
||||
"${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true",
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
fun fetchUrl(url: String, authenticator : Boolean): String {
|
||||
fun getTraffic(context: Context, location: Location, carOrientation: Float): String {
|
||||
val useAsset = false
|
||||
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
|
||||
return if (useAsset) {
|
||||
val trafficJson = context.resources.openRawResource(R.raw.tomtom_traffic)
|
||||
trafficJson.bufferedReader().use { it.readText() }
|
||||
} else {
|
||||
val trafficResult = fetchUrl(
|
||||
"$tomtomUrl?key=$tomtomApiKey&bbox=$bbox&fields=$tomtomFields&language=en-GB&timeValidityFilter=present",
|
||||
false
|
||||
)
|
||||
trafficResult.replace(
|
||||
"{\"incidents\":",
|
||||
"{\"type\": \"FeatureCollection\", \"features\":"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchUrl(url: String, authenticator: Boolean): String {
|
||||
try {
|
||||
if (authenticator) {
|
||||
Authenticator.setDefault(object : Authenticator() {
|
||||
@@ -79,7 +118,7 @@ abstract class NavigationRepository {
|
||||
val responseCode = httpURLConnection.responseCode
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
val response = httpURLConnection.inputStream.bufferedReader()
|
||||
.use { it.readText() } // defaults to UTF-8
|
||||
.use { it.readText() }
|
||||
return response
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.kouros.navigation.data
|
||||
|
||||
import android.location.Location
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||
import com.kouros.navigation.data.osrm.OsrmRoute
|
||||
@@ -8,9 +7,10 @@ import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Maneuver
|
||||
import com.kouros.navigation.data.route.Step
|
||||
import com.kouros.navigation.data.route.Summary
|
||||
import com.kouros.navigation.data.tomtom.TomTomResponse
|
||||
import com.kouros.navigation.data.tomtom.TomTomRoute
|
||||
import com.kouros.navigation.data.valhalla.ValhallaResponse
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRoute
|
||||
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
@@ -38,6 +38,7 @@ data class Route(
|
||||
|
||||
}
|
||||
fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine }
|
||||
|
||||
fun route(route: String) = apply {
|
||||
if (route.isNotEmpty() && route != "[]") {
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
@@ -52,11 +53,14 @@ data class Route(
|
||||
)
|
||||
ValhallaRoute().mapJsonToValhalla(routeJson, this)
|
||||
}
|
||||
|
||||
else -> {
|
||||
RouteEngine.OSRM.ordinal -> {
|
||||
val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
|
||||
OsrmRoute().mapToOsrm(osrmJson, this)
|
||||
}
|
||||
else -> {
|
||||
val tomtomJson = gson.fromJson(route, TomTomResponse::class.java)
|
||||
TomTomRoute().mapToOsrm(tomtomJson, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,9 +75,7 @@ data class Route(
|
||||
fun buildEmpty(): Route {
|
||||
return Route(
|
||||
routeEngine = 0,
|
||||
//summary = Summary(0.0, 0.0),
|
||||
routes = emptyList(),
|
||||
// legs = emptyList(),
|
||||
//waypoints = emptyList(),
|
||||
//routeGeoJson = "",
|
||||
)
|
||||
@@ -81,14 +83,18 @@ data class Route(
|
||||
}
|
||||
|
||||
|
||||
val legs: List<Leg>
|
||||
get() = routes.first().legs
|
||||
|
||||
fun legs(): List<Leg> {
|
||||
return if (routes.isNotEmpty()) {
|
||||
routes.first().legs
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun currentStep(): Step {
|
||||
|
||||
return if (legs.isNotEmpty()) {
|
||||
legs.first().steps[currentStep]
|
||||
return if (routes.isNotEmpty() && legs().isNotEmpty()) {
|
||||
legs().first().steps[currentStep]
|
||||
} else {
|
||||
Step(maneuver = Maneuver(waypoints = emptyList(), location = location(0.0, 0.0)))
|
||||
}
|
||||
@@ -96,8 +102,8 @@ data class Route(
|
||||
|
||||
fun nextStep(): Step {
|
||||
val nextIndex = currentStep + 1
|
||||
return if (nextIndex < legs.first().steps.size) {
|
||||
legs.first().steps[nextIndex]
|
||||
return if (nextIndex < legs().first().steps.size) {
|
||||
legs().first().steps[nextIndex]
|
||||
} else {
|
||||
throw IndexOutOfBoundsException("No next maneuver available.")
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Intersections(
|
||||
|
||||
@SerializedName("in") var inV: Int? = null,
|
||||
@SerializedName("out") var out: Int? = null,
|
||||
@SerializedName("in") var inV: Int = 0,
|
||||
@SerializedName("out") var out: Int = 0,
|
||||
@SerializedName("entry") var entry: ArrayList<Boolean> = arrayListOf(),
|
||||
@SerializedName("bearings") var bearings: ArrayList<Int> = arrayListOf(),
|
||||
@SerializedName("location") var location: ArrayList<Double> = arrayListOf(),
|
||||
|
||||
@@ -6,9 +6,9 @@ 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
|
||||
@SerializedName("weight" ) var weight : Double = 0.0,
|
||||
@SerializedName("summary" ) var summary : String = "",
|
||||
@SerializedName("duration" ) var duration : Double = 0.0,
|
||||
@SerializedName("distance" ) var distance : Double = 0.0
|
||||
|
||||
)
|
||||
@@ -3,12 +3,13 @@ package com.kouros.navigation.data.osrm
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Maneuver (
|
||||
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
|
||||
@SerializedName("bearing_after") var bearingAfter: Int = 0,
|
||||
@SerializedName("bearing_before") var bearingBefore: Int = 0,
|
||||
@SerializedName("location") var location: ArrayList<Double> = arrayListOf(),
|
||||
@SerializedName("modifier") var modifier: String = "",
|
||||
@SerializedName("type") var type: String = "",
|
||||
@SerializedName("exit") var exit: Int = 0,
|
||||
|
||||
)
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kouros.navigation.data.osrm
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.SearchFilter
|
||||
@@ -8,6 +9,7 @@ private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/"
|
||||
|
||||
class OsrmRepository : NavigationRepository() {
|
||||
override fun getRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
carOrientation: Float,
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OsrmResponse (
|
||||
|
||||
@SerializedName("code" ) var code : String? = null,
|
||||
@SerializedName("code" ) var code : String = "",
|
||||
@SerializedName("routes" ) var routes : ArrayList<Routes> = arrayListOf(),
|
||||
@SerializedName("waypoints" ) var waypoints : ArrayList<Waypoints> = arrayListOf()
|
||||
|
||||
|
||||
@@ -16,57 +16,55 @@ class OsrmRoute {
|
||||
|
||||
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
|
||||
|
||||
|
||||
val routes = mutableListOf<com.kouros.navigation.data.route.Routes>()
|
||||
var stepIndex = 0
|
||||
routeJson.routes.forEach { route ->
|
||||
val legs = mutableListOf<Leg>()
|
||||
val waypoints = mutableListOf<List<Double>>()
|
||||
val summary = Summary(route.duration!!, route.distance!! / 1000)
|
||||
val summary = Summary(route.duration, route.distance / 1000)
|
||||
route.legs.forEach { leg ->
|
||||
val steps = mutableListOf<Step>()
|
||||
leg.steps.forEach { step ->
|
||||
val intersections = mutableListOf<Intersection>()
|
||||
if (step.maneuver != null) {
|
||||
val points = decodePolyline(step.geometry!!, 5)
|
||||
waypoints.addAll(points)
|
||||
val maneuver = RouteManeuver(
|
||||
bearingBefore = step.maneuver.bearingBefore ?: 0,
|
||||
bearingAfter = step.maneuver.bearingAfter ?: 0,
|
||||
type = convertType(step.maneuver),
|
||||
waypoints = points,
|
||||
location = location(
|
||||
step.maneuver.location[0],
|
||||
step.maneuver.location[1]
|
||||
)
|
||||
val points = decodePolyline(step.geometry, 5)
|
||||
waypoints.addAll(points)
|
||||
val maneuver = RouteManeuver(
|
||||
bearingBefore = step.maneuver.bearingBefore,
|
||||
bearingAfter = step.maneuver.bearingAfter,
|
||||
type = convertType(step.maneuver),
|
||||
waypoints = points,
|
||||
exit = step.maneuver.exit,
|
||||
location = location(
|
||||
step.maneuver.location[0],
|
||||
step.maneuver.location[1]
|
||||
)
|
||||
step.intersections.forEach { it2 ->
|
||||
if (it2.location[0] != 0.0) {
|
||||
val lanes = mutableListOf<Lane>()
|
||||
it2.lanes.forEach { it3 ->
|
||||
if (it3.indications.isNotEmpty() && it3.indications.first() != "none") {
|
||||
val lane = Lane(
|
||||
location(it2.location[0], it2.location[1]),
|
||||
it3.valid,
|
||||
it3.indications
|
||||
)
|
||||
lanes.add(lane)
|
||||
}
|
||||
)
|
||||
step.intersections.forEach { it2 ->
|
||||
if (it2.location[0] != 0.0) {
|
||||
val lanes = mutableListOf<Lane>()
|
||||
it2.lanes.forEach { it3 ->
|
||||
if (it3.indications.isNotEmpty() && it3.indications.first() != "none") {
|
||||
val lane = Lane(
|
||||
location(it2.location[0], it2.location[1]),
|
||||
it3.valid,
|
||||
it3.indications
|
||||
)
|
||||
lanes.add(lane)
|
||||
}
|
||||
intersections.add(Intersection(it2.location, lanes))
|
||||
}
|
||||
intersections.add(Intersection(it2.location, lanes))
|
||||
}
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
name = step.name!!,
|
||||
distance = step.distance!! / 1000,
|
||||
duration = step.duration!!,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections
|
||||
)
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
}
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
name = step.name,
|
||||
distance = step.distance / 1000,
|
||||
duration = step.duration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections
|
||||
)
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
}
|
||||
legs.add(Leg(steps))
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ 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
|
||||
@SerializedName("weight" ) var weight : Double = 0.0,
|
||||
@SerializedName("duration" ) var duration : Double = 0.0,
|
||||
@SerializedName("distance" ) var distance : Double = 0.0
|
||||
|
||||
)
|
||||
@@ -6,13 +6,13 @@ 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" ) val 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
|
||||
@SerializedName("driving_side" ) var drivingSide : String = "",
|
||||
@SerializedName("geometry" ) var geometry : String = "",
|
||||
@SerializedName("maneuver" ) val maneuver : Maneuver = Maneuver(),
|
||||
@SerializedName("name" ) var name : String = "",
|
||||
@SerializedName("mode" ) var mode : String = "",
|
||||
@SerializedName("weight" ) var weight : Double = 0.0,
|
||||
@SerializedName("duration" ) var duration : Double = 0.0,
|
||||
@SerializedName("distance" ) var distance : Double = 0.0,
|
||||
|
||||
)
|
||||
@@ -5,9 +5,9 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Waypoints (
|
||||
|
||||
@SerializedName("hint" ) var hint : String? = null,
|
||||
@SerializedName("hint" ) var hint : String = "",
|
||||
@SerializedName("location" ) var location : ArrayList<Double> = arrayListOf(),
|
||||
@SerializedName("name" ) var name : String? = null,
|
||||
@SerializedName("distance" ) var distance : Double? = null
|
||||
@SerializedName("name" ) var name : String = "",
|
||||
@SerializedName("distance" ) var distance : Double = 0.0,
|
||||
|
||||
)
|
||||
@@ -5,10 +5,10 @@ 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("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
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ package com.kouros.navigation.data.overpass
|
||||
|
||||
import android.location.Location
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.navigation.utils.GeoUtils.getOverpassBbox
|
||||
import kotlinx.serialization.json.Json
|
||||
import com.kouros.navigation.utils.GeoUtils.getBoundingBox
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
@@ -41,7 +40,7 @@ class Overpass {
|
||||
location: Location,
|
||||
radius: Double
|
||||
): List<Elements> {
|
||||
val boundingBox = getOverpassBbox(location, radius)
|
||||
val boundingBox = getBoundingBox(location.latitude, location.longitude, radius)
|
||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
||||
httpURLConnection.requestMethod = "POST"
|
||||
httpURLConnection.setRequestProperty(
|
||||
|
||||
@@ -18,7 +18,7 @@ data class Tags(
|
||||
@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? = null,
|
||||
@SerializedName("maxspeed") var maxspeed: String = "0",
|
||||
@SerializedName("direction") var direction: String? = null,
|
||||
|
||||
)
|
||||
@@ -8,4 +8,5 @@ data class Maneuver(
|
||||
val type: Int = 0,
|
||||
val waypoints: List<List<Double>>,
|
||||
val location: Location,
|
||||
val exit: Int = 0,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Events (
|
||||
|
||||
@SerializedName("description" ) var description : String? = null
|
||||
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Features (
|
||||
|
||||
@SerializedName("type" ) var type : String? = null,
|
||||
@SerializedName("properties" ) var properties : Properties? = Properties(),
|
||||
@SerializedName("geometry" ) var geometry : Geometry? = Geometry()
|
||||
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Geometry (
|
||||
|
||||
@SerializedName("type" ) var type : String? = null,
|
||||
@SerializedName("coordinates" ) var coordinates : List<List<Double>> = arrayListOf()
|
||||
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Incidents (
|
||||
|
||||
@SerializedName("type" ) var type : String? = null,
|
||||
@SerializedName("properties" ) var properties : Properties? = Properties(),
|
||||
@SerializedName("geometry" ) var geometry : Geometry? = Geometry()
|
||||
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Properties (
|
||||
|
||||
@SerializedName("iconCategory" ) var iconCategory : Int? = null,
|
||||
@SerializedName("events" ) var events : ArrayList<Events> = arrayListOf()
|
||||
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Report(
|
||||
val effectiveSettings: List<EffectiveSetting>
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.kouros.navigation.data.Route
|
||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||
import com.kouros.navigation.data.osrm.OsrmRoute.ManeuverType
|
||||
import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Maneuver
|
||||
import com.kouros.navigation.data.route.Step
|
||||
import com.kouros.navigation.data.route.Summary
|
||||
import com.kouros.navigation.utils.GeoUtils.decodePolyline
|
||||
import com.kouros.navigation.utils.location
|
||||
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
||||
|
||||
/**
|
||||
curl -X GET "https://api.tomtom.com/routing/1/calculateRoute/\
|
||||
48.1856548,11.57928:48.1183,11.59485/json?\
|
||||
vehicleHeading=90§ionType=traffic\
|
||||
&report=effectiveSettings&routeType=eco\
|
||||
&traffic=true&avoid=unpavedRoadimport com.kouros.navigation.data.route.Maneuver as RouteManeuvers&travelMode=car\
|
||||
&vehicleMaxSpeed=120&vehicleCommercial=false\
|
||||
&instructionsType=text&language=en-GB§ionType=lanes\
|
||||
&routeRepresentation=encodedPolyline\
|
||||
&vehicleEngineType=combustion&key=678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
*/
|
||||
|
||||
class TomTomRoute {
|
||||
|
||||
fun mapToOsrm(routeJson: TomTomResponse, builder: Route.Builder) {
|
||||
routeJson.routes.forEach { route ->
|
||||
val legs = mutableListOf<Leg>()
|
||||
val waypoints = mutableListOf<List<Double>>()
|
||||
var points = listOf<List<Double>>()
|
||||
val summary = Summary(
|
||||
route.summary.travelTimeInSeconds.toDouble(),
|
||||
route.summary.lengthInMeters.toDouble() / 1000
|
||||
)
|
||||
route.legs.forEach { leg ->
|
||||
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
|
||||
waypoints.addAll(points)
|
||||
}
|
||||
route.guidance.instructions.forEach { instruction ->
|
||||
instruction.exitNumber
|
||||
// val maneuver = RouteManeuver(
|
||||
// // bearingBefore = step.maneuver.bearingBefore,
|
||||
// //bearingAfter = step.maneuver.bearingAfter,
|
||||
// type = convertType(instruction.maneuver),
|
||||
// waypoints = points.subList(section.startPointIndex, section.endPointIndex + 1),
|
||||
// exit = instruction.exitNumber.toInt(),
|
||||
// location = location(
|
||||
// instruction.point.longitude, instruction.point.latitude
|
||||
// )
|
||||
// )
|
||||
}
|
||||
route.sections.forEach { section ->
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
println(routeJson)
|
||||
}
|
||||
|
||||
fun convertType(type: String): Int {
|
||||
var newType = 0
|
||||
when (type) {
|
||||
"DEPART" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DEPART
|
||||
}
|
||||
}
|
||||
return newType
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
data class Traffic (
|
||||
|
||||
//@SerializedName("incidents" ) var incidents : ArrayList<Incidents> = arrayListOf()
|
||||
@SerializedName("type" ) var type : String = "",
|
||||
@SerializedName("features" ) var features : ArrayList<Features> = arrayListOf()
|
||||
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class TrafficData (
|
||||
var traffic : Traffic ,
|
||||
var trafficData: String = ""
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kouros.navigation.data.valhalla
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.kouros.navigation.data.Locations
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
@@ -13,6 +14,7 @@ private const val routeUrl = "https://kouros-online.de/valhalla/route?json="
|
||||
class ValhallaRepository : NavigationRepository() {
|
||||
|
||||
override fun getRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
carOrientation: Float,
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.kouros.navigation.data.route.Lane
|
||||
import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Routes
|
||||
import com.kouros.navigation.data.valhalla.ManeuverType
|
||||
import com.kouros.navigation.utils.Levenshtein
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -128,16 +129,24 @@ open class RouteModel() {
|
||||
|
||||
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val instruction = currentStep().instruction
|
||||
val levenshtein = Levenshtein()
|
||||
// speed limit
|
||||
val distance = lastSpeedLocation.distanceTo(location)
|
||||
if (distance > 500 || lastSpeedIndex < route.currentStep) {
|
||||
lastSpeedIndex = route.currentStep
|
||||
lastSpeedLocation = location
|
||||
val elements = viewModel.getMaxSpeed(location)
|
||||
elements.forEach {
|
||||
if (it.tags.name != null && it.tags.maxspeed != null) {
|
||||
val speed = it.tags.maxspeed!!.toInt()
|
||||
maxSpeed = speed
|
||||
if (it.tags.name != null) {
|
||||
if (isNavigating()) {
|
||||
val distance =
|
||||
levenshtein.distance(it.tags.name!!, instruction)
|
||||
if (distance < 5) {
|
||||
val speed = it.tags.maxspeed.toInt()
|
||||
maxSpeed = speed
|
||||
lastSpeedLocation = location
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +159,7 @@ open class RouteModel() {
|
||||
val distanceToNextStep = leftStepDistance()
|
||||
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
|
||||
val shouldAdvance =
|
||||
isNearNextManeuver && route.currentStep < (route.legs.first().steps.size)
|
||||
isNearNextManeuver && route.currentStep < (route.legs().first().steps.size)
|
||||
|
||||
// Determine the maneuver type and corresponding icon
|
||||
var curManeuverType = if (hasArrived(currentStep.maneuver.type)) {
|
||||
@@ -166,17 +175,16 @@ open class RouteModel() {
|
||||
}
|
||||
// Safely get the street name from the maneuver
|
||||
val streetName = relevantStep.name
|
||||
var exitNumber = currentStep.maneuver.exit
|
||||
if (shouldAdvance) {
|
||||
curManeuverType = relevantStep.maneuver.type
|
||||
exitNumber = relevantStep.maneuver.exit
|
||||
}
|
||||
val maneuverIcon = maneuverIcon(curManeuverType)
|
||||
maneuverType = curManeuverType
|
||||
|
||||
val lanes = currentLanes(location)
|
||||
|
||||
if (lanes.isNotEmpty())
|
||||
println("Street: $streetName Dist: $distanceToNextStep Lane: ${lanes.size}")
|
||||
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
streetName,
|
||||
@@ -185,9 +193,9 @@ open class RouteModel() {
|
||||
maneuverIcon,
|
||||
arrivalTime(),
|
||||
travelLeftDistance(),
|
||||
lanes
|
||||
lanes,
|
||||
exitNumber
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -215,7 +223,10 @@ open class RouteModel() {
|
||||
maneuverType,
|
||||
maneuverIcon,
|
||||
arrivalTime(),
|
||||
travelLeftDistance()
|
||||
travelLeftDistance(),
|
||||
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
|
||||
step.maneuver.exit
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -263,7 +274,7 @@ open class RouteModel() {
|
||||
fun travelLeftDistance(): Double {
|
||||
var leftDistance = 0.0
|
||||
for (i in route.currentStep + 1..<curLeg.steps.size) {
|
||||
val step = route.legs!![0].steps[i]
|
||||
val step = route.legs()[0].steps[i]
|
||||
leftDistance += step.distance
|
||||
}
|
||||
leftDistance += leftStepDistance() / 1000
|
||||
|
||||
@@ -9,7 +9,6 @@ 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.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.ObjectBox.boxStore
|
||||
import com.kouros.navigation.data.Place
|
||||
@@ -19,12 +18,16 @@ 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.data.overpass.Overpass
|
||||
import com.kouros.navigation.data.tomtom.Features
|
||||
import com.kouros.navigation.data.tomtom.Traffic
|
||||
import com.kouros.navigation.data.tomtom.TrafficData
|
||||
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.location
|
||||
import io.objectbox.kotlin.boxFor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.maplibre.geojson.FeatureCollection
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@@ -34,6 +37,11 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val traffic: MutableLiveData<Map<String, String> > by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
|
||||
val previewRoute: MutableLiveData<String> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
@@ -156,6 +164,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
try {
|
||||
route.postValue(
|
||||
repository.getRoute(
|
||||
context,
|
||||
currentLocation,
|
||||
location,
|
||||
carOrientation,
|
||||
@@ -168,11 +177,46 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadTraffic(context: Context, currentLocation: Location, carOrientation : Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val data = repository.getTraffic(
|
||||
context,
|
||||
currentLocation,
|
||||
carOrientation
|
||||
)
|
||||
val trafficData = rebuildTraffic(data)
|
||||
traffic.postValue(
|
||||
trafficData
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rebuildTraffic(data: String) : Map<String, String> {
|
||||
val featureCollection = FeatureCollection.fromJson(data)
|
||||
val incidents = mutableMapOf<String, String>()
|
||||
val queuing = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Queuing traffic")}
|
||||
incidents["queuing"] = FeatureCollection.fromFeatures(queuing).toJson()
|
||||
val stationary = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Stationary traffic")}
|
||||
incidents["stationary"] = FeatureCollection.fromFeatures(stationary).toJson()
|
||||
val slow = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Slow traffic")}
|
||||
incidents["slow"] = FeatureCollection.fromFeatures(slow).toJson()
|
||||
val heavy = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Heavy traffic")}
|
||||
incidents["heavy"] = FeatureCollection.fromFeatures(heavy).toJson()
|
||||
val roadworks = featureCollection.features()!!.filter { it.properties()!!.get("events").toString().contains("Roadworks")}
|
||||
incidents["roadworks"] = FeatureCollection.fromFeatures(roadworks).toJson()
|
||||
|
||||
return incidents
|
||||
}
|
||||
fun loadPreviewRoute(context: Context, currentLocation: Location, location: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
previewRoute.postValue(
|
||||
repository.getRoute(
|
||||
context,
|
||||
currentLocation,
|
||||
location,
|
||||
carOrientation,
|
||||
|
||||
@@ -115,45 +115,31 @@ object GeoUtils {
|
||||
return featureCollection.toJson()
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the lat and len of a square around a point.
|
||||
* @return latMin, latMax, lngMin, lngMax
|
||||
*/
|
||||
fun calculateSquareRadius(lat: Double, lng: Double, radius: Double): DoubleArray {
|
||||
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)))
|
||||
|
||||
return doubleArrayOf(latMin, latMax, lngMin, lngMax)
|
||||
return "$lngMin,$latMin,$lngMax,$latMax"
|
||||
}
|
||||
fun getBoundingBox(
|
||||
lat: Double,
|
||||
lon: Double,
|
||||
radius: Double
|
||||
): Map<String, Map<String, 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)))
|
||||
|
||||
return mapOf(
|
||||
"nw" to mapOf("lat" to maxLat, "lon" to minLon),
|
||||
"ne" to mapOf("lat" to maxLat, "lon" to maxLon),
|
||||
"sw" to mapOf("lat" to minLat, "lon" to minLon),
|
||||
"se" to mapOf("lat" to minLat, "lon" to maxLon)
|
||||
)
|
||||
return "$minLat,$minLon,$maxLat,$maxLon"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.kouros.navigation.utils
|
||||
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* The Levenshtein distance between two words is the minimum number of single-character edits (insertions, deletions or
|
||||
* substitutions) required to change one string into the other.
|
||||
*
|
||||
* This implementation uses dynamic programming (Wagner–Fischer algorithm).
|
||||
*
|
||||
* [Levenshtein Distance](https://en.wikipedia.org/wiki/Levenshtein_distance)
|
||||
*/
|
||||
class Levenshtein {
|
||||
|
||||
/**
|
||||
* The Levenshtein distance, or edit distance, between two words is the minimum number of single-character edits
|
||||
* (insertions, deletions or substitutions) required to change one word into the other.
|
||||
*
|
||||
* It is always at least the difference of the sizes of the two strings.
|
||||
* It is at most the length of the longer string.
|
||||
* It is `0` if and only if the strings are equal.
|
||||
*
|
||||
* @param first first string to compare.
|
||||
* @param second second string to compare.
|
||||
* @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 {
|
||||
if (first == second) return 0
|
||||
if (first.isEmpty()) return second.length
|
||||
if (second.isEmpty()) return first.length
|
||||
|
||||
// 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)
|
||||
var cost = IntArray(first.length + 1) { it }
|
||||
var newCost = IntArray(first.length + 1)
|
||||
|
||||
for (i in 1..second.length) {
|
||||
// calculate new costs from the previous row.
|
||||
// the first element of the new row is the edit distance (deletes) to match empty string
|
||||
newCost[0] = i
|
||||
var minCost = i
|
||||
|
||||
// fill in the rest of the row
|
||||
for (j in 1..first.length) {
|
||||
// if it's the same char at the same position, no edit cost.
|
||||
val edit = if (first[j - 1] == second[i - 1]) 0 else 1
|
||||
val replace = cost[j - 1] + edit
|
||||
val insert = cost[j] + 1
|
||||
val delete = newCost[j - 1] + 1
|
||||
newCost[j] = minOf(insert, delete, replace)
|
||||
minCost = min(minCost, newCost[j])
|
||||
}
|
||||
|
||||
if (minCost >= limit) return limit
|
||||
// flip references of current and previous row
|
||||
val swap = cost
|
||||
cost = newCost
|
||||
newCost = swap
|
||||
}
|
||||
return cost.last()
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import java.time.LocalDateTime
|
||||
@@ -29,7 +30,8 @@ object NavigationUtils {
|
||||
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
|
||||
return when (routeEngine) {
|
||||
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
||||
else -> ViewModel(OsrmRepository())
|
||||
RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository())
|
||||
else -> ViewModel(TomTomRepository())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,17 +69,7 @@
|
||||
"source-layer": "landuse",
|
||||
"maxzoom": 12,
|
||||
"filter": ["==", ["get", "class"], "residential"],
|
||||
"paint": {
|
||||
"fill-color": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
9,
|
||||
"hsla(0,3%,85%,0.84)",
|
||||
12,
|
||||
"hsla(35,57%,88%,0.49)"
|
||||
]
|
||||
}
|
||||
"paint": {"fill-color": "rgba(48, 43, 57, 1)"}
|
||||
},
|
||||
{
|
||||
"id": "landcover_wood",
|
||||
@@ -89,7 +79,7 @@
|
||||
"filter": ["==", ["get", "class"], "wood"],
|
||||
"paint": {
|
||||
"fill-antialias": false,
|
||||
"fill-color": "hsla(98,61%,72%,0.7)",
|
||||
"fill-color": "rgba(21, 28, 16, 0.7)",
|
||||
"fill-opacity": 0.4
|
||||
}
|
||||
},
|
||||
@@ -1303,15 +1293,7 @@
|
||||
],
|
||||
"layout": {"line-cap": "round", "line-join": "round"},
|
||||
"paint": {
|
||||
"line-color": [
|
||||
"interpolate",
|
||||
["linear"],
|
||||
["zoom"],
|
||||
5,
|
||||
"hsl(26,87%,62%)",
|
||||
6,
|
||||
"#ab9"
|
||||
],
|
||||
"line-color": "rgba(12, 84, 84, 1)",
|
||||
"line-width": [
|
||||
"interpolate",
|
||||
["exponential", 1.2],
|
||||
@@ -2457,7 +2439,8 @@
|
||||
"text-field": ["to-string", ["get", "ref"]],
|
||||
"text-font": ["Noto Sans Regular"],
|
||||
"text-rotation-alignment": "viewport",
|
||||
"text-size": 10
|
||||
"text-size": 10,
|
||||
"visibility": "none"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2585,7 +2568,8 @@
|
||||
"text-letter-spacing": 0.1,
|
||||
"text-max-width": 9,
|
||||
"text-size": ["interpolate", ["linear"], ["zoom"], 8, 9, 12, 10],
|
||||
"text-transform": "uppercase"
|
||||
"text-transform": "uppercase",
|
||||
"visibility": "none"
|
||||
},
|
||||
"paint": {
|
||||
"text-color": "#333",
|
||||
|
||||
603
common/data/src/main/res/raw/tomom_routing.json
Normal file
603
common/data/src/main/res/raw/tomom_routing.json
Normal file
@@ -0,0 +1,603 @@
|
||||
{
|
||||
"formatVersion": "0.0.12",
|
||||
"report": {
|
||||
"effectiveSettings": [
|
||||
{
|
||||
"key": "avoid",
|
||||
"value": "unpavedRoads"
|
||||
},
|
||||
{
|
||||
"key": "computeBestOrder",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"key": "computeTollAmounts",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"key": "computeTravelTimeFor",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"key": "contentType",
|
||||
"value": "json"
|
||||
},
|
||||
{
|
||||
"key": "departAt",
|
||||
"value": "2026-01-29T08:43:35.397Z"
|
||||
},
|
||||
{
|
||||
"key": "guidanceVersion",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"key": "includeTollPaymentTypes",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"key": "instructionsType",
|
||||
"value": "text"
|
||||
},
|
||||
{
|
||||
"key": "language",
|
||||
"value": "en-GB"
|
||||
},
|
||||
{
|
||||
"key": "locations",
|
||||
"value": "48.18565,11.57928:48.11830,11.59485"
|
||||
},
|
||||
{
|
||||
"key": "maxAlternatives",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "routeRepresentation",
|
||||
"value": "encodedPolyline"
|
||||
},
|
||||
{
|
||||
"key": "routeType",
|
||||
"value": "eco"
|
||||
},
|
||||
{
|
||||
"key": "sectionType",
|
||||
"value": "lanes"
|
||||
},
|
||||
{
|
||||
"key": "sectionType",
|
||||
"value": "traffic"
|
||||
},
|
||||
{
|
||||
"key": "traffic",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"key": "travelMode",
|
||||
"value": "car"
|
||||
},
|
||||
{
|
||||
"key": "vehicleAxleWeight",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "vehicleCommercial",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"key": "vehicleEngineType",
|
||||
"value": "combustion"
|
||||
},
|
||||
{
|
||||
"key": "vehicleHeading",
|
||||
"value": "90"
|
||||
},
|
||||
{
|
||||
"key": "vehicleHeight",
|
||||
"value": "0.00"
|
||||
},
|
||||
{
|
||||
"key": "vehicleLength",
|
||||
"value": "0.00"
|
||||
},
|
||||
{
|
||||
"key": "vehicleMaxSpeed",
|
||||
"value": "120"
|
||||
},
|
||||
{
|
||||
"key": "vehicleNumberOfAxles",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "vehicleWeight",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "vehicleWidth",
|
||||
"value": "0.00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"summary": {
|
||||
"lengthInMeters": 10879,
|
||||
"travelTimeInSeconds": 1170,
|
||||
"trafficDelayInSeconds": 76,
|
||||
"trafficLengthInMeters": 1727,
|
||||
"departureTime": "2026-01-29T09:43:35+01:00",
|
||||
"arrivalTime": "2026-01-29T10:03:05+01:00"
|
||||
},
|
||||
"legs": [
|
||||
{
|
||||
"summary": {
|
||||
"lengthInMeters": 10879,
|
||||
"travelTimeInSeconds": 1170,
|
||||
"trafficDelayInSeconds": 76,
|
||||
"trafficLengthInMeters": 1727,
|
||||
"departureTime": "2026-01-29T09:43:35+01:00",
|
||||
"arrivalTime": "2026-01-29T10:03:05+01:00"
|
||||
},
|
||||
"encodedPolyline": "sfbeHmqteAEjDQEy@GQ?wDQFkEH{G?M?sA@kB?_FAeC?o@?[@_@\\Ab@CVAz@CJA@?dBGhAGjAExAGlBEvBKdCKTAfCKLAv@ELA|AGnAGt@ClCKjDQpBIf@BXDPDPBF@ZB?S@SAYAUKi@Go@Cc@BgBBs@Bg@JyAJiAXqBDWNs@TaA\\mAFa@Po@`BwF?YL]FSFSl@iB^kALc@L]Ro@f@yAf@{AFQNe@dAoCdBgEx@qBTi@BITe@L[L_@^}@HSXu@pB}El@eAb@e@f@[h@QZCRAL?j@HRFh@Vf@XLJhAn@lAv@TLtAz@r@`@bAn@pAv@f@X~@j@z@h@vBpA`@VHDFFJFf@X`CzApAh@f@L`@Fz@@f@AXEVEVEhA[h@Yn@e@PQFEJKRWV[PW`@w@t@}AHQN]~BiFP]`AoBh@aADGTa@t@aAt@{@PQJKJGFG@Cd@]XSxDmCf@a@n@o@TY\\g@LQHMJSLUP[f@iAPg@b@yAFODMNi@FS|@qCVaAHUHUn@wBHYh@eBpAkEjBiGfAeDj@yADMFQd@sAf@kAJUh@qAf@eAt@sAn@iALSN[p@kAVc@JOLSj@w@z@}@x@q@pAu@p@_@j@Sl@MLCRCb@E`@?^?L@`ABz@?N@~AFdADJ@rAH`DVpCVrAJd@BfHp@zGl@pAJ|ALnGp@jEh@fBJpAFF?P@N@ZCtC]r@GJCFCLCD?TEVEXGhAYzAg@NGv@]`@QJEvB_AXMVK\\Qb@Qn@QJCNAZC^ENA`@FnBb@xEtA^H^JnCl@z@r@`@Pr@TtBlA~C`Bn@\\xAl@PF`@LrAVlCh@bBLl@BlBJdG\\RDjCHn@?pB?xB?R@`@@GxAC^?ZInBIfCAvC?r@@dD@n@@b@@^D`C?TDxAFbBHdB@VHp@RjAJb@NNH`@VlBFf@PzARhBFd@@LRbBFh@\\nC@FNhAb@lEj@tDPpABTBRZlBTdBXjBn@xEBLDTRpAR~@Fb@",
|
||||
"encodedPolylinePrecision": 5
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"startPointIndex": 83,
|
||||
"endPointIndex": 147,
|
||||
"sectionType": "TRAFFIC",
|
||||
"simpleCategory": "JAM",
|
||||
"effectiveSpeedInKmh": 35,
|
||||
"delayInSeconds": 76,
|
||||
"magnitudeOfDelay": 1,
|
||||
"tec": {
|
||||
"causes": [
|
||||
{
|
||||
"mainCauseCode": 1
|
||||
}
|
||||
],
|
||||
"effectCode": 4
|
||||
},
|
||||
"eventId": "TTL41048054144049000"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
"directions": [
|
||||
"SLIGHT_LEFT"
|
||||
],
|
||||
"follow": "SLIGHT_LEFT"
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"laneSeparators": [
|
||||
"SINGLE_SOLID",
|
||||
"SINGLE_SOLID",
|
||||
"LONG_DASHED",
|
||||
"LONG_DASHED",
|
||||
"SINGLE_SOLID"
|
||||
],
|
||||
"startPointIndex": 42,
|
||||
"endPointIndex": 45,
|
||||
"sectionType": "LANES"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
],
|
||||
"follow": "STRAIGHT"
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"SLIGHT_RIGHT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"laneSeparators": [
|
||||
"SINGLE_SOLID",
|
||||
"SHORT_DASHED",
|
||||
"SINGLE_SOLID"
|
||||
],
|
||||
"startPointIndex": 61,
|
||||
"endPointIndex": 62,
|
||||
"sectionType": "LANES"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
"directions": [
|
||||
"SLIGHT_LEFT"
|
||||
],
|
||||
"follow": "SLIGHT_LEFT"
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"SLIGHT_LEFT"
|
||||
],
|
||||
"follow": "SLIGHT_LEFT"
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"SLIGHT_RIGHT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"laneSeparators": [
|
||||
"SINGLE_SOLID",
|
||||
"LONG_DASHED",
|
||||
"SHORT_DASHED",
|
||||
"SINGLE_SOLID"
|
||||
],
|
||||
"startPointIndex": 74,
|
||||
"endPointIndex": 75,
|
||||
"sectionType": "LANES"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"SLIGHT_RIGHT"
|
||||
],
|
||||
"follow": "SLIGHT_RIGHT"
|
||||
}
|
||||
],
|
||||
"laneSeparators": [
|
||||
"SINGLE_SOLID",
|
||||
"LONG_DASHED",
|
||||
"SINGLE_SOLID"
|
||||
],
|
||||
"startPointIndex": 265,
|
||||
"endPointIndex": 266,
|
||||
"sectionType": "LANES"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
"directions": [
|
||||
"LEFT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"RIGHT"
|
||||
],
|
||||
"follow": "RIGHT"
|
||||
}
|
||||
],
|
||||
"laneSeparators": [
|
||||
"SINGLE_SOLID",
|
||||
"SINGLE_SOLID",
|
||||
"SINGLE_SOLID",
|
||||
"SINGLE_SOLID",
|
||||
"SINGLE_SOLID"
|
||||
],
|
||||
"startPointIndex": 287,
|
||||
"endPointIndex": 288,
|
||||
"sectionType": "LANES"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
"directions": [
|
||||
"LEFT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
],
|
||||
"follow": "STRAIGHT"
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"STRAIGHT"
|
||||
],
|
||||
"follow": "STRAIGHT"
|
||||
},
|
||||
{
|
||||
"directions": [
|
||||
"RIGHT"
|
||||
]
|
||||
}
|
||||
],
|
||||
"laneSeparators": [
|
||||
"SINGLE_SOLID",
|
||||
"SHORT_DASHED",
|
||||
"LONG_DASHED",
|
||||
"SHORT_DASHED",
|
||||
"SINGLE_SOLID"
|
||||
],
|
||||
"startPointIndex": 302,
|
||||
"endPointIndex": 304,
|
||||
"sectionType": "LANES"
|
||||
}
|
||||
],
|
||||
"guidance": {
|
||||
"instructions": [
|
||||
{
|
||||
"routeOffsetInMeters": 0,
|
||||
"travelTimeInSeconds": 0,
|
||||
"point": {
|
||||
"latitude": 48.18554,
|
||||
"longitude": 11.57927
|
||||
},
|
||||
"exitNumber": "",
|
||||
"pointIndex": 0,
|
||||
"instructionType": "LOCATION_DEPARTURE",
|
||||
"street": "Vogelhartstraße",
|
||||
"countryCode": "DEU",
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "DEPART",
|
||||
"message": "Leave from Vogelhartstraße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 64,
|
||||
"travelTimeInSeconds": 14,
|
||||
"point": {
|
||||
"latitude": 48.18557,
|
||||
"longitude": 11.57841
|
||||
},
|
||||
"pointIndex": 1,
|
||||
"instructionType": "TURN",
|
||||
"street": "Silcherstraße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": 90,
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "TURN_RIGHT",
|
||||
"message": "Turn right onto Silcherstraße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 218,
|
||||
"travelTimeInSeconds": 57,
|
||||
"point": {
|
||||
"latitude": 48.18696,
|
||||
"longitude": 11.57857
|
||||
},
|
||||
"pointIndex": 5,
|
||||
"instructionType": "TURN",
|
||||
"street": "Schmalkaldener Straße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": 90,
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "TURN_RIGHT",
|
||||
"message": "Turn right onto Schmalkaldener Straße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 650,
|
||||
"travelTimeInSeconds": 131,
|
||||
"point": {
|
||||
"latitude": 48.18686,
|
||||
"longitude": 11.58437
|
||||
},
|
||||
"pointIndex": 15,
|
||||
"instructionType": "TURN",
|
||||
"roadNumbers": [
|
||||
"B13"
|
||||
],
|
||||
"street": "Ingolstädter Straße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": 90,
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "TURN_RIGHT",
|
||||
"message": "Turn right onto Ingolstädter Straße/B13"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 1713,
|
||||
"travelTimeInSeconds": 266,
|
||||
"point": {
|
||||
"latitude": 48.17733,
|
||||
"longitude": 11.58503
|
||||
},
|
||||
"pointIndex": 45,
|
||||
"instructionType": "TURN",
|
||||
"roadNumbers": [
|
||||
"B2R"
|
||||
],
|
||||
"street": "Schenkendorfstraße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": -90,
|
||||
"possibleCombineWithNext": true,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "TURN_LEFT",
|
||||
"message": "Turn left onto Schenkendorfstraße/B2R",
|
||||
"combinedMessage": "Turn left onto Schenkendorfstraße/B2R then keep left at Schenkendorfstraße/B2R toward Messe / ICM"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 2067,
|
||||
"travelTimeInSeconds": 309,
|
||||
"point": {
|
||||
"latitude": 48.17678,
|
||||
"longitude": 11.58957
|
||||
},
|
||||
"pointIndex": 62,
|
||||
"instructionType": "TURN",
|
||||
"roadNumbers": [
|
||||
"B2R"
|
||||
],
|
||||
"street": "Schenkendorfstraße",
|
||||
"countryCode": "DEU",
|
||||
"signpostText": "Messe / ICM",
|
||||
"junctionType": "BIFURCATION",
|
||||
"turnAngleInDecimalDegrees": -45,
|
||||
"possibleCombineWithNext": true,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "KEEP_LEFT",
|
||||
"message": "Keep left at Schenkendorfstraße/B2R toward Messe / ICM",
|
||||
"combinedMessage": "Keep left at Schenkendorfstraße/B2R toward Messe / ICM then keep left at Schenkendorfstraße/B2R toward Passau"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 2419,
|
||||
"travelTimeInSeconds": 332,
|
||||
"point": {
|
||||
"latitude": 48.17518,
|
||||
"longitude": 11.59363
|
||||
},
|
||||
"pointIndex": 75,
|
||||
"instructionType": "TURN",
|
||||
"roadNumbers": [
|
||||
"B2R"
|
||||
],
|
||||
"street": "Schenkendorfstraße",
|
||||
"countryCode": "DEU",
|
||||
"signpostText": "Passau",
|
||||
"junctionType": "BIFURCATION",
|
||||
"turnAngleInDecimalDegrees": -45,
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "KEEP_LEFT",
|
||||
"message": "Keep left at Schenkendorfstraße/B2R toward Passau"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 2774,
|
||||
"travelTimeInSeconds": 357,
|
||||
"point": {
|
||||
"latitude": 48.17329,
|
||||
"longitude": 11.59747
|
||||
},
|
||||
"pointIndex": 86,
|
||||
"instructionType": "DIRECTION_INFO",
|
||||
"roadNumbers": [
|
||||
"B2R"
|
||||
],
|
||||
"street": "Isarring",
|
||||
"countryCode": "DEU",
|
||||
"signpostText": "München-Ost",
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "FOLLOW",
|
||||
"message": "Follow Isarring/B2R toward München-Ost"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 8425,
|
||||
"travelTimeInSeconds": 806,
|
||||
"point": {
|
||||
"latitude": 48.13017,
|
||||
"longitude": 11.61541
|
||||
},
|
||||
"pointIndex": 266,
|
||||
"instructionType": "TURN",
|
||||
"street": "Ampfingstraße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": 45,
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "BEAR_RIGHT",
|
||||
"message": "Bear right at Ampfingstraße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 9487,
|
||||
"travelTimeInSeconds": 953,
|
||||
"point": {
|
||||
"latitude": 48.12089,
|
||||
"longitude": 11.61285
|
||||
},
|
||||
"pointIndex": 288,
|
||||
"instructionType": "TURN",
|
||||
"street": "Anzinger Straße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": 90,
|
||||
"possibleCombineWithNext": true,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "TURN_RIGHT",
|
||||
"message": "Turn right onto Anzinger Straße",
|
||||
"combinedMessage": "Turn right onto Anzinger Straße then keep straight on at Sankt-Martin-Straße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 9983,
|
||||
"travelTimeInSeconds": 1044,
|
||||
"point": {
|
||||
"latitude": 48.12087,
|
||||
"longitude": 11.60621
|
||||
},
|
||||
"pointIndex": 304,
|
||||
"instructionType": "TURN",
|
||||
"street": "Sankt-Martin-Straße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": 0,
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "STRAIGHT",
|
||||
"message": "Keep straight on at Sankt-Martin-Straße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 10879,
|
||||
"travelTimeInSeconds": 1170,
|
||||
"point": {
|
||||
"latitude": 48.1183,
|
||||
"longitude": 11.59485
|
||||
},
|
||||
"pointIndex": 335,
|
||||
"instructionType": "LOCATION_ARRIVAL",
|
||||
"street": "Sankt-Martin-Straße",
|
||||
"countryCode": "DEU",
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "ARRIVE",
|
||||
"message": "You have arrived at Sankt-Martin-Straße"
|
||||
}
|
||||
],
|
||||
"instructionGroups": [
|
||||
{
|
||||
"firstInstructionIndex": 0,
|
||||
"lastInstructionIndex": 3,
|
||||
"groupMessage": "Leave from Vogelhartstraße. Take the Ingolstädter Straße/B13",
|
||||
"groupLengthInMeters": 1713
|
||||
},
|
||||
{
|
||||
"firstInstructionIndex": 4,
|
||||
"lastInstructionIndex": 7,
|
||||
"groupMessage": "Take the Schenkendorfstraße, Isarring/B2R toward Messe / ICM, Passau, München-Ost",
|
||||
"groupLengthInMeters": 6712
|
||||
},
|
||||
{
|
||||
"firstInstructionIndex": 8,
|
||||
"lastInstructionIndex": 11,
|
||||
"groupMessage": "Take the Ampfingstraße, Anzinger Straße. Continue to your destination at Sankt-Martin-Straße",
|
||||
"groupLengthInMeters": 2454
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
4578
common/data/src/main/res/raw/tomtom_traffic.json
Normal file
4578
common/data/src/main/res/raw/tomtom_traffic.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -48,5 +48,6 @@
|
||||
<string name="charging_station">Ladestation</string>
|
||||
<string name="speed_camera">Speed camera</string>
|
||||
<string name="use_car_location">Auto GPS verwenden</string>
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -34,4 +34,5 @@
|
||||
<string name="osrm" translatable="false">Osrm</string>
|
||||
<string name="routing_engine" translatable="false">Routing engine</string>
|
||||
<string name="use_car_location">Use car location</string>
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user