Before TomTom Routing
This commit is contained in:
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.kouros.navigation"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 28
|
||||
versionName = "0.1.3.28"
|
||||
versionCode = 32
|
||||
versionName = "0.1.3.32"
|
||||
base.archivesName = "navi-$versionName"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.di
|
||||
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
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.BaseStyleModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
@@ -14,4 +15,5 @@ val appModule = module {
|
||||
viewModelOf(::ViewModel)
|
||||
singleOf(::ValhallaRepository)
|
||||
singleOf(::OsrmRepository)
|
||||
singleOf(::TomTomRepository)
|
||||
}
|
||||
@@ -78,6 +78,8 @@ class MainActivity : ComponentActivity() {
|
||||
val routeModel = RouteModel()
|
||||
var tilt = 50.0
|
||||
val useMock = false
|
||||
|
||||
var currentIndex = 0
|
||||
val stepData: MutableLiveData<StepData> by lazy {
|
||||
MutableLiveData<StepData>()
|
||||
}
|
||||
@@ -113,7 +115,7 @@ class MainActivity : ComponentActivity() {
|
||||
lateinit var baseStyle: BaseStyle.Json
|
||||
|
||||
init {
|
||||
navigationViewModel.route.observe(this, observer)
|
||||
|
||||
}
|
||||
|
||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
@@ -127,13 +129,14 @@ class MainActivity : ComponentActivity() {
|
||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
fusedLocationClient.lastLocation
|
||||
.addOnSuccessListener { location: android.location.Location? ->
|
||||
.addOnSuccessListener { _: android.location.Location? ->
|
||||
if (useMock) {
|
||||
mock = MockLocation(locationManager)
|
||||
mock.setMockLocation(
|
||||
homeHohenwaldeck.latitude,
|
||||
homeHohenwaldeck.longitude
|
||||
homeVogelhart.latitude,
|
||||
homeVogelhart.longitude
|
||||
)
|
||||
navigationViewModel.route.observe(this, observer)
|
||||
}
|
||||
}
|
||||
enableEdgeToEdge()
|
||||
@@ -156,7 +159,12 @@ class MainActivity : ComponentActivity() {
|
||||
Content()
|
||||
// auto navigate
|
||||
if (useMock) {
|
||||
navigationViewModel.loadRoute(applicationContext, homeHohenwaldeck, homeVogelhart, 0F)
|
||||
navigationViewModel.loadRoute(
|
||||
applicationContext,
|
||||
homeVogelhart,
|
||||
homeHohenwaldeck,
|
||||
0F
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -249,6 +257,9 @@ class MainActivity : ComponentActivity() {
|
||||
&& lastLocation.longitude != location.position.longitude
|
||||
) {
|
||||
val currentLocation = location(location.position.longitude, location.position.latitude)
|
||||
// if (currentIndex == 0)
|
||||
// navigationViewModel.loadTraffic(applicationContext, currentLocation, 0f)
|
||||
// currentIndex = 1
|
||||
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||
with(routeModel) {
|
||||
if (isNavigating()) {
|
||||
@@ -326,7 +337,7 @@ class MainActivity : ComponentActivity() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||
if (routeModel.isNavigating()) {
|
||||
var deviation = 0.0
|
||||
val deviation = 0.0
|
||||
if (index in 0..routeModel.curRoute.waypoints.size) {
|
||||
mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
|
||||
delay(500L) //
|
||||
@@ -359,9 +370,10 @@ class MainActivity : ComponentActivity() {
|
||||
fun test2() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
// Balanstr.
|
||||
mock.setMockLocation( 48.119357, 11.599130)
|
||||
mock.setMockLocation(48.119357, 11.599130)
|
||||
}
|
||||
}
|
||||
|
||||
fun gpx(context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val parser = GPXParser()
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.kouros.navigation.car.map.MapLibre
|
||||
import com.kouros.navigation.car.map.NavigationImage
|
||||
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.data.tomtom.TrafficData
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
import org.maplibre.compose.location.LocationTrackingEffect
|
||||
@@ -65,6 +66,7 @@ fun MapView(
|
||||
cameraState,
|
||||
rememberBaseStyle,
|
||||
route,
|
||||
emptyMap(),
|
||||
ViewStyle.VIEW
|
||||
)
|
||||
LocationTrackingEffect(
|
||||
|
||||
@@ -28,6 +28,10 @@ fun NavigationInfo(step: StepData?, nextStep: StepData?) {
|
||||
contentDescription = stringResource(id = R.string.accept_action_title),
|
||||
modifier = Modifier.size(48.dp, 48.dp),
|
||||
)
|
||||
if (step.currentManeuverType == 46
|
||||
|| step.currentManeuverType == 45) {
|
||||
Text(text ="Exit ${step.exitNumber}", fontSize = 20.sp)
|
||||
}
|
||||
Column {
|
||||
if (step.leftStepDistance < 1000) {
|
||||
Text(text = "${step.leftStepDistance.toInt()} m", fontSize = 25.sp)
|
||||
|
||||
@@ -57,6 +57,15 @@ fun NavigationSheet(
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
}
|
||||
Button(onClick = {
|
||||
simulateNavigation()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_zoom_in_24),
|
||||
"Stop",
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.size(30.dp))
|
||||
if (!routeModel.isNavigating()) {
|
||||
|
||||
@@ -30,21 +30,18 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.car.screen.NavigationScreen
|
||||
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||
import com.kouros.navigation.car.screen.SearchScreen
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.CAR_LOCATION
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.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.BaseStyleModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
|
||||
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
@@ -93,8 +90,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
lateinit var navigationViewModel: ViewModel
|
||||
|
||||
lateinit var baseStyle: BaseStyle.Json
|
||||
|
||||
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
|
||||
OnCarDataAvailableListener { data ->
|
||||
if (data.location.status == CarValue.STATUS_SUCCESS) {
|
||||
@@ -129,21 +124,21 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
fun onRoutingEngineStateUpdated(routeEngine : Int) {
|
||||
navigationViewModel = when (routeEngine) {
|
||||
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
||||
else -> ViewModel(OsrmRepository())
|
||||
RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository())
|
||||
else -> ViewModel(TomTomRepository())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
|
||||
navigationViewModel = getViewModel(carContext)
|
||||
|
||||
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
|
||||
|
||||
routeModel = RouteCarModel()
|
||||
|
||||
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
|
||||
baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, baseStyle)
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
|
||||
|
||||
navigationScreen =
|
||||
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
|
||||
|
||||
@@ -29,10 +29,13 @@ import com.kouros.navigation.car.map.cameraState
|
||||
import com.kouros.navigation.car.map.getPaddingValues
|
||||
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.tomtom.TrafficData
|
||||
import com.kouros.navigation.model.BaseStyleModel
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.utils.bearing
|
||||
@@ -49,8 +52,7 @@ import org.maplibre.spatialk.geojson.Position
|
||||
|
||||
class SurfaceRenderer(
|
||||
private var carContext: CarContext, lifecycle: Lifecycle,
|
||||
private var routeModel: RouteCarModel,
|
||||
private var baseStyle: BaseStyle.Json
|
||||
private var routeModel: RouteCarModel
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
var lastLocation = location(0.0, 0.0)
|
||||
@@ -70,14 +72,20 @@ class SurfaceRenderer(
|
||||
var height = 0
|
||||
var lastBearing = 0.0
|
||||
val routeData = MutableLiveData("")
|
||||
|
||||
val trafficData = MutableLiveData(emptyMap<String, String>())
|
||||
val speedCamerasData = MutableLiveData("")
|
||||
val speed = MutableLiveData(0F)
|
||||
lateinit var centerLocation: Location
|
||||
var viewStyle = ViewStyle.VIEW
|
||||
lateinit var centerLocation: Location
|
||||
var previewDistance = 0.0
|
||||
lateinit var mapView: ComposeView
|
||||
var tilt = 55.0
|
||||
var countDownTimerActive = false
|
||||
|
||||
val style: MutableLiveData<BaseStyle> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
|
||||
|
||||
lateinit var lifecycleOwner: CustomLifecycleOwner
|
||||
@@ -160,6 +168,7 @@ class SurfaceRenderer(
|
||||
init {
|
||||
lifecycle.addObserver(this)
|
||||
speed.value = 0F
|
||||
|
||||
}
|
||||
|
||||
fun onConnectionStateUpdated(connectionState: Int) {
|
||||
@@ -170,16 +179,24 @@ class SurfaceRenderer(
|
||||
}
|
||||
}
|
||||
|
||||
fun onBaseStyleStateUpdated(style: BaseStyle) {
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MapView() {
|
||||
//println("DarkMode ${carContext.isDarkMode}")
|
||||
|
||||
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
|
||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||
|
||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||
val route: String? by routeData.observeAsState()
|
||||
val traffic: Map<String, String> ? by trafficData.observeAsState()
|
||||
val speedCameras: String? by speedCamerasData.observeAsState()
|
||||
val paddingValues = getPaddingValues(height, viewStyle)
|
||||
val cameraState = cameraState(paddingValues, position, tilt)
|
||||
val rememberBaseStyle = rememberBaseStyle(baseStyle)
|
||||
MapLibre(carContext, cameraState, rememberBaseStyle, route, viewStyle, speedCameras)
|
||||
MapLibre(carContext, cameraState, rememberBaseStyle, route, traffic, viewStyle, speedCameras)
|
||||
ShowPosition(cameraState, position, paddingValues)
|
||||
}
|
||||
|
||||
@@ -217,6 +234,7 @@ class SurfaceRenderer(
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
CarConnection(carContext).type.observe(owner, ::onConnectionStateUpdated)
|
||||
style.observe(owner, :: onBaseStyleStateUpdated)
|
||||
Log.i(TAG, "SurfaceRenderer created")
|
||||
carContext.getCarService(AppManager::class.java)
|
||||
.setSurfaceCallback(mSurfaceCallback)
|
||||
@@ -288,6 +306,10 @@ class SurfaceRenderer(
|
||||
viewStyle = ViewStyle.VIEW
|
||||
}
|
||||
|
||||
fun setTrafficData(traffic: Map<String, String> ) {
|
||||
trafficData.value = traffic as MutableMap<String, String>?
|
||||
}
|
||||
|
||||
fun setPreviewRouteData(routeModel: RouteModel) {
|
||||
viewStyle = ViewStyle.PREVIEW
|
||||
with(routeModel) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.car.map
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.car.app.connection.CarConnection
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -10,18 +11,15 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.drawText
|
||||
@@ -34,19 +32,21 @@ import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||
import com.kouros.navigation.data.NavigationColor
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
import com.kouros.navigation.data.RouteColor
|
||||
import com.kouros.navigation.data.SpeedColor
|
||||
import com.kouros.navigation.model.BaseStyleModel
|
||||
import com.kouros.navigation.data.tomtom.TrafficData
|
||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
import org.maplibre.compose.expressions.ast.Expression
|
||||
import org.maplibre.compose.expressions.dsl.const
|
||||
import org.maplibre.compose.expressions.dsl.exponential
|
||||
import org.maplibre.compose.expressions.dsl.image
|
||||
import org.maplibre.compose.expressions.dsl.interpolate
|
||||
import org.maplibre.compose.expressions.dsl.zoom
|
||||
import org.maplibre.compose.expressions.value.ColorValue
|
||||
import org.maplibre.compose.layers.Anchor
|
||||
import org.maplibre.compose.layers.FillLayer
|
||||
import org.maplibre.compose.layers.LineLayer
|
||||
@@ -92,10 +92,10 @@ fun MapLibre(
|
||||
cameraState: CameraState,
|
||||
baseStyle: BaseStyle.Json,
|
||||
route: String?,
|
||||
traffic: Map<String, String> ?,
|
||||
viewStyle: ViewStyle,
|
||||
speedCameras: String? = ""
|
||||
) {
|
||||
|
||||
MaplibreMap(
|
||||
options = MapOptions(
|
||||
ornamentOptions =
|
||||
@@ -111,7 +111,7 @@ fun MapLibre(
|
||||
if (viewStyle == ViewStyle.AMENITY_VIEW) {
|
||||
AmenityLayer(route)
|
||||
} else {
|
||||
RouteLayer(route)
|
||||
RouteLayer(route, traffic!!)
|
||||
}
|
||||
SpeedCameraLayer(speedCameras)
|
||||
}
|
||||
@@ -121,7 +121,7 @@ fun MapLibre(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RouteLayer(routeData: String?) {
|
||||
fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
||||
if (routeData != null && routeData.isNotEmpty()) {
|
||||
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
||||
LineLayer(
|
||||
@@ -153,6 +153,49 @@ fun RouteLayer(routeData: String?) {
|
||||
),
|
||||
)
|
||||
}
|
||||
trafficData.forEach {
|
||||
val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value))
|
||||
LineLayer(
|
||||
id = "traffic-${it.key}-casing",
|
||||
source = traffic,
|
||||
color = const(Color.White),
|
||||
width =
|
||||
interpolate(
|
||||
type = exponential(1.2f),
|
||||
input = zoom(),
|
||||
5 to const(0.4.dp),
|
||||
6 to const(0.8.dp),
|
||||
7 to const(2.0.dp),
|
||||
20 to const(24.dp),
|
||||
),
|
||||
)
|
||||
LineLayer(
|
||||
id = "traffic-${it.key}",
|
||||
source = traffic,
|
||||
color = trafficColor(it.key),
|
||||
width =
|
||||
interpolate(
|
||||
type = exponential(1.2f),
|
||||
input = zoom(),
|
||||
5 to const(0.4.dp),
|
||||
6 to const(0.7.dp),
|
||||
7 to const(1.75.dp),
|
||||
20 to const(22.dp),
|
||||
),
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun trafficColor(key: String): Expression<ColorValue> {
|
||||
when (key) {
|
||||
"queuing" -> return const(Color(0xFFD24417))
|
||||
"stationary" -> return const(Color(0xFFFF0000))
|
||||
"heavy" -> return const(Color(0xFF6B0404))
|
||||
"slow" -> return const(Color(0xFFC41F1F))
|
||||
"roadworks" -> return const(Color(0xFF7A631A))
|
||||
}
|
||||
return const(Color.Blue)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -270,7 +313,7 @@ private fun CurrentSpeed(
|
||||
curSpeed: Float,
|
||||
maxSpeed: Int
|
||||
) {
|
||||
val radius = 32
|
||||
val radius = 34
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
@@ -336,7 +379,7 @@ private fun MaxSpeed(
|
||||
height: Int,
|
||||
maxSpeed: Int,
|
||||
) {
|
||||
val radius = 20
|
||||
val radius = 24
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.kouros.navigation.car.navigation
|
||||
|
||||
import android.text.SpannableString
|
||||
import android.text.Spanned
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.car.app.AppManager
|
||||
@@ -26,22 +27,25 @@ import androidx.car.app.model.Alert
|
||||
import androidx.car.app.model.AlertCallback
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.CarIconSpan
|
||||
import androidx.car.app.model.CarText
|
||||
import androidx.car.app.model.DateTimeWithZone
|
||||
import androidx.car.app.model.Distance
|
||||
import androidx.car.app.navigation.model.Lane
|
||||
import androidx.car.app.navigation.model.LaneDirection
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
|
||||
import androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
||||
import androidx.car.app.navigation.model.Step
|
||||
import androidx.car.app.navigation.model.TravelEstimate
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import org.maplibre.compose.expressions.dsl.step
|
||||
import java.util.Collections
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.text.trim
|
||||
|
||||
/** A class that provides models for the routing demos. */
|
||||
class RouteCarModel() : RouteModel() {
|
||||
@@ -51,12 +55,17 @@ class RouteCarModel() : RouteModel() {
|
||||
val stepData = currentStep()
|
||||
val currentStepCueWithImage: SpannableString =
|
||||
createString(stepData.instruction)
|
||||
|
||||
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
||||
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) {
|
||||
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
|
||||
}
|
||||
val step =
|
||||
Step.Builder(currentStepCueWithImage)
|
||||
.setManeuver(
|
||||
Maneuver.Builder(stepData.currentManeuverType)
|
||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||
.build()
|
||||
maneuver.build()
|
||||
)
|
||||
if (destination.street != null) {
|
||||
step.setRoad(destination.street!!)
|
||||
@@ -72,12 +81,16 @@ class RouteCarModel() : RouteModel() {
|
||||
val stepData = nextStep()
|
||||
val currentStepCueWithImage: SpannableString =
|
||||
createString(stepData.instruction)
|
||||
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
||||
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) {
|
||||
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
|
||||
}
|
||||
val step =
|
||||
Step.Builder(currentStepCueWithImage)
|
||||
.setManeuver(
|
||||
Maneuver.Builder(stepData.currentManeuverType)
|
||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||
.build()
|
||||
maneuver.build()
|
||||
)
|
||||
.build()
|
||||
return step
|
||||
@@ -109,8 +122,8 @@ class RouteCarModel() : RouteModel() {
|
||||
timeToDestinationMillis
|
||||
)
|
||||
)
|
||||
.setRemainingTimeColor(CarColor.YELLOW)
|
||||
.setRemainingDistanceColor(CarColor.RED)
|
||||
.setRemainingTimeColor(CarColor.GREEN)
|
||||
.setRemainingDistanceColor(CarColor.BLUE)
|
||||
|
||||
if (travelMessage.isNotEmpty()) {
|
||||
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
|
||||
@@ -203,6 +216,18 @@ class RouteCarModel() : RouteModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStringWithIcon(
|
||||
carContext: CarContext,
|
||||
text: String,
|
||||
@DrawableRes iconRes: Int
|
||||
): SpannableString {
|
||||
val start = 0
|
||||
val end = text.length
|
||||
val span = CarIconSpan.create(createCarIcon(carContext, iconRes), CarIconSpan.ALIGN_CENTER)
|
||||
val spannableString = SpannableString(text)
|
||||
spannableString.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannableString
|
||||
}
|
||||
fun createString(
|
||||
text: String
|
||||
): SpannableString {
|
||||
@@ -222,7 +247,7 @@ class RouteCarModel() : RouteModel() {
|
||||
return CarIcon.Builder(iconCompat).build()
|
||||
}
|
||||
|
||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
|
||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
|
||||
carContext.getCarService<AppManager?>(AppManager::class.java)
|
||||
.showAlert(createAlert(carContext, distance, maxSpeed, createCarIcon(carContext, R.drawable.speed_camera_24px)))
|
||||
}
|
||||
|
||||
@@ -31,10 +31,16 @@ import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.nominatim.SearchResult
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.navigation.data.tomtom.Features
|
||||
import com.kouros.navigation.data.tomtom.Geometry
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.utils.GeoUtils
|
||||
import com.kouros.navigation.utils.location
|
||||
import java.time.LocalDateTime
|
||||
import java.time.Period
|
||||
import java.time.ZoneOffset
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Duration
|
||||
|
||||
class NavigationScreen(
|
||||
carContext: CarContext,
|
||||
@@ -55,6 +61,7 @@ class NavigationScreen(
|
||||
var recentPlace = Place()
|
||||
var navigationType = NavigationType.VIEW
|
||||
|
||||
var lastTrafficDate = LocalDateTime.of(1960, 6, 21, 0, 0)
|
||||
val observer = Observer<String> { route ->
|
||||
if (route.isNotEmpty()) {
|
||||
navigationType = NavigationType.NAVIGATION
|
||||
@@ -71,6 +78,10 @@ class NavigationScreen(
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
val trafficObserver = Observer<Map<String, String> > { traffic ->
|
||||
surfaceRenderer.setTrafficData(traffic)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
val placeObserver = Observer<SearchResult> { searchResult ->
|
||||
val place = Place(
|
||||
@@ -101,6 +112,7 @@ class NavigationScreen(
|
||||
|
||||
init {
|
||||
viewModel.route.observe(this, observer)
|
||||
viewModel.traffic.observe(this, trafficObserver);
|
||||
viewModel.recentPlace.observe(this, recentObserver)
|
||||
viewModel.placeLocation.observe(this, placeObserver)
|
||||
viewModel.speedCameras.observe(this, speedObserver)
|
||||
@@ -471,7 +483,14 @@ class NavigationScreen(
|
||||
}
|
||||
|
||||
fun updateTrip(location: Location) {
|
||||
updateSpeedCamera(surfaceRenderer.lastLocation)
|
||||
val current = LocalDateTime.now(ZoneOffset.UTC)
|
||||
val duration = java.time.Duration.between(current, lastTrafficDate)
|
||||
if (duration.abs().seconds > 360) {
|
||||
lastTrafficDate = current
|
||||
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
||||
}
|
||||
//updateTraffic(location)
|
||||
updateSpeedCamera(location)
|
||||
with(routeModel) {
|
||||
updateLocation(location, viewModel)
|
||||
if ((maneuverType == Maneuver.TYPE_DESTINATION
|
||||
@@ -512,11 +531,14 @@ class NavigationScreen(
|
||||
}
|
||||
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
||||
val camera = sortedList.first()
|
||||
val bearingSpeedCamera = location.bearingTo(location(camera.lon!!, camera.lat!!))
|
||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
||||
|
||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||
camera.tags.direction!!.toFloat()
|
||||
} else {
|
||||
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
||||
}
|
||||
if (camera.distance < 80) {
|
||||
if ((bearingSpeedCamera.absoluteValue - bearingRoute.absoluteValue).absoluteValue < 20.0) {
|
||||
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
|
||||
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,6 @@ class RoutePreviewScreen(
|
||||
|
||||
private fun createRouteText(index: Int): CarText {
|
||||
val time = routeModel.route.routes[index].summary.duration
|
||||
println("Duration $time")
|
||||
val length =
|
||||
BigDecimal(routeModel.route.routes[index].summary.distance).setScale(
|
||||
1,
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
|
||||
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
|
||||
|
||||
class RoutingSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
|
||||
private var routingEngine = RouteEngine.VALHALLA.ordinal
|
||||
private var routingEngine = RouteEngine.OSRM.ordinal
|
||||
|
||||
|
||||
init {
|
||||
@@ -45,6 +45,11 @@ class RoutingSettings(private val carContext: CarContext, private var viewModel:
|
||||
R.string.osrm,
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
buildRowForTemplate(
|
||||
R.string.tomtom,
|
||||
)
|
||||
)
|
||||
.setOnSelectedListener { index: Int ->
|
||||
this.onSelected(index)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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)
|
||||
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,25 +16,24 @@ 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)
|
||||
val points = decodePolyline(step.geometry, 5)
|
||||
waypoints.addAll(points)
|
||||
val maneuver = RouteManeuver(
|
||||
bearingBefore = step.maneuver.bearingBefore ?: 0,
|
||||
bearingAfter = step.maneuver.bearingAfter ?: 0,
|
||||
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]
|
||||
@@ -58,16 +57,15 @@ class OsrmRoute {
|
||||
}
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
name = step.name!!,
|
||||
distance = step.distance!! / 1000,
|
||||
duration = step.duration!!,
|
||||
name = step.name,
|
||||
distance = step.distance / 1000,
|
||||
duration = step.duration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections
|
||||
)
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
}
|
||||
}
|
||||
legs.add(Leg(steps))
|
||||
}
|
||||
val routeGeoJson = createLineStringCollection(waypoints)
|
||||
|
||||
@@ -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()
|
||||
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