This commit is contained in:
Dimitris
2026-01-10 12:48:41 +01:00
parent 7efa2685be
commit e274011080
35 changed files with 1877 additions and 571 deletions

View File

@@ -30,17 +30,19 @@ 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.ROUTING_ENGINE
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.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.getRouteEngine
import com.kouros.navigation.utils.NavigationUtils.getViewModel
import org.maplibre.compose.style.BaseStyle
class NavigationSession : Session(), NavigationScreen.Listener {
@@ -54,33 +56,32 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var surfaceRenderer: SurfaceRenderer
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
// if (routingEngine == RouteEngine.VALHALLA.ordinal) {
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION)
if (!useCarLocation) {
updateLocation(location!!)
// }
}
}
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "In onCreate()")
}
override fun onResume(owner: LifecycleOwner) {
Log.i(TAG, "In onResume()")
}
override fun onPause(owner: LifecycleOwner) {
Log.i(TAG, "In onPause()")
}
override fun onStop(owner: LifecycleOwner) {
Log.i(TAG, "In onStop()")
}
override fun onDestroy(owner: LifecycleOwner) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
carSensors.removeCarHardwareLocationListener(carLocationListener)
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION)
if (useCarLocation) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
carSensors.removeCarHardwareLocationListener(carLocationListener)
}
carInfo.removeSpeedListener(carSpeedListener)
Log.i(TAG, "In onDestroy()")
val locationManager =
@@ -123,7 +124,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
override fun onCreateScreen(intent: Intent): Screen {
navigationViewModel = getRouteEngine(carContext)
navigationViewModel = getViewModel(carContext)
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
routeModel = RouteCarModel()
@@ -167,13 +168,16 @@ class NavigationSession : Session(), NavigationScreen.Listener {
}
fun addSensors() {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
carSensors.addCarHardwareLocationListener(
CarSensors.UPDATE_RATE_NORMAL,
carContext.mainExecutor,
carLocationListener
)
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION)
if (useCarLocation) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
carSensors.addCarHardwareLocationListener(
CarSensors.UPDATE_RATE_FASTEST,
carContext.mainExecutor,
carLocationListener
)
}
carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
}
@@ -229,7 +233,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
updateLocation(location)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
/* minTimeMs= */ 1000,
/* minTimeMs= */ 500,
/* minDistanceM= */ 5f,
mLocationListener
)
@@ -239,16 +243,18 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun updateLocation(location: Location) {
if (routeModel.isNavigating()) {
navigationScreen.updateTrip(location)
val wayPointLocation = routeModel.route.currentStep().wayPointLocation
val distance = location.distanceTo(wayPointLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) {
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
return
}
if (distance < MAXIMAL_SNAP_CORRECTION) {
surfaceRenderer.updateLocation(wayPointLocation)
} else {
surfaceRenderer.updateLocation(location)
if (!routeModel.arrived) {
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val distance = location.distanceTo(snapedLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) {
navigationScreen.calculateNewRoute(routeModel.destination)
return
}
if (distance < MAXIMAL_SNAP_CORRECTION) {
surfaceRenderer.updateLocation(snapedLocation)
} else {
surfaceRenderer.updateLocation(location)
}
}
} else {
surfaceRenderer.updateLocation(location)

View File

@@ -170,6 +170,7 @@ class SurfaceRenderer(
@Composable
fun MapView() {
//println("DarkMode ${carContext.isDarkMode}")
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val speedCameras: String? by speedCamerasData.observeAsState()
@@ -193,7 +194,7 @@ class SurfaceRenderer(
DrawNavigationImages(
paddingValues,
currentSpeed,
routeModel.routeState.maxSpeed,
routeModel.maxSpeed,
width,
height
)

View File

@@ -58,7 +58,7 @@ class RouteCarModel() : RouteModel() {
.setIcon(createCarIcon(carContext, stepData.icon))
.build()
)
.setRoad(routeState.destination.street!!)
.setRoad(destination.street!!)
if (stepData.lane.isNotEmpty()) {
addLanes(carContext, step, stepData)
}
@@ -110,9 +110,9 @@ class RouteCarModel() : RouteModel() {
.setRemainingTimeColor(CarColor.YELLOW)
.setRemainingDistanceColor(CarColor.RED)
if (routeState.travelMessage.isNotEmpty()) {
if (travelMessage.isNotEmpty()) {
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
travelBuilder.setTripText(CarText.create(routeState.travelMessage))
travelBuilder.setTripText(CarText.create(travelMessage))
}
return travelBuilder.build()
}
@@ -235,8 +235,7 @@ class RouteCarModel() : RouteModel() {
R.string.exit_action_title, R.string.exit_action_title,
FLAG_DEFAULT
)
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */10000)
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */5000)
.setSubtitle(subtitle)
.setIcon(icon)
.addAction(dismissAction).setCallback(object : AlertCallback {

View File

@@ -141,11 +141,11 @@ class NavigationScreen(
}
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
if (routeModel.routeState.arrived) {
if (routeModel.arrived) {
val timer = object : CountDownTimer(8000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
routeModel.routeState = routeModel.routeState.copy(arrived = false)
routeModel.arrived = false
navigationType = NavigationType.VIEW
invalidate()
}
@@ -164,8 +164,8 @@ class NavigationScreen(
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
var street = ""
if (routeModel.routeState.destination.street != null) {
street = routeModel.routeState.destination.street!!
if (routeModel.destination.street != null) {
street = routeModel.destination.street!!
}
return NavigationTemplate.Builder()
.setNavigationInfo(
@@ -298,7 +298,7 @@ class NavigationScreen(
.setOnClickListener {
val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo)
routeModel.routeState = routeModel.routeState.copy(destination = recentPlace)
routeModel.destination = recentPlace
}
.build()
}
@@ -417,7 +417,7 @@ class NavigationScreen(
viewModel.saveRecent(place)
currentNavigationLocation = location
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
routeModel.routeState = routeModel.routeState.copy(destination = place)
routeModel.destination = place
invalidate()
}
@@ -454,11 +454,14 @@ class NavigationScreen(
updateSpeedCamera(surfaceRenderer.lastLocation)
with(routeModel) {
updateLocation(location, viewModel)
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
if ((maneuverType == Maneuver.TYPE_DESTINATION
|| maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|| maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
stopNavigation()
routeState = routeState.copy(arrived = true)
arrived = true
surfaceRenderer.routeData.value = ""
navigationType = NavigationType.ARRIVAL
invalidate()

View File

@@ -12,21 +12,29 @@ import androidx.car.app.model.Toggle
import com.kouros.data.R
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY
import com.kouros.navigation.data.Constants.CAR_LOCATION
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) :
Screen(carContext) {
private var motorWayToggleState = false
private var tollWayToggleState = false
private var carLocationToggleState = false
init {
motorWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY)
tollWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY)
carLocationToggleState = getBooleanKeyValue(carContext, CAR_LOCATION)
}
override fun onGetTemplate(): Template {
@@ -53,6 +61,24 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod
tollWayToggleState = !tollWayToggleState
}.setChecked(tollWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
val carLocationToggle: Toggle =
Toggle.Builder { checked: Boolean ->
if (checked) {
setBooleanKeyValue(carContext, true, CAR_LOCATION)
} else {
setBooleanKeyValue(carContext, false, CAR_LOCATION)
}
carLocationToggleState = !carLocationToggleState
}.setChecked(carLocationToggleState).build()
listBuilder.addItem(
buildRowForTemplate(
R.string.use_car_location,
carLocationToggle
)
)
listBuilder.addItem(
buildRowForScreenTemplate(
RoutingSettings(carContext, viewModel),

View File

@@ -7,7 +7,6 @@ import android.text.SpannableString
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Distance
@@ -22,15 +21,11 @@ import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.CONTACTS
import com.kouros.navigation.data.Constants.FAVORITES
import com.kouros.navigation.data.Constants.RECENT
import com.kouros.navigation.data.Constants.categories
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel
import kotlin.math.min
class PlaceListScreen(

View File

@@ -10,18 +10,25 @@ import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.SectionedItemList
import androidx.car.app.model.Template
import androidx.car.app.model.Toggle
import com.kouros.data.R
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
import com.kouros.navigation.data.Constants.CAR_LOCATION
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
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
init {
routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
}
override fun onGetTemplate(): Template {
@@ -58,7 +65,6 @@ class RoutingSettings(private val carContext: CarContext, private var viewModel:
.build()
}
private fun onSelected(index: Int) {
setIntKeyValue(carContext, index, ROUTING_ENGINE)
viewModel.routingEngine.value = index

View File

@@ -1,255 +0,0 @@
{
"trip": {
"locations": [
{
"type": "break",
"lat": 48.185749,
"lon": 11.579374,
"side_of_street": "right",
"original_index": 0
},
{
"type": "break",
"lat": 48.116481,
"lon": 11.594322,
"street": "Hohenwaldeckstr. 27",
"side_of_street": "left",
"original_index": 1
}
],
"legs": [
{
"maneuvers": [
{
"type": 2,
"instruction": "Auf Vogelhartstraße Richtung Westen fahren.",
"verbal_succinct_transition_instruction": "Richtung Westen fahren. Dann Rechts auf Silcherstraße abbiegen.",
"verbal_pre_transition_instruction": "Auf Vogelhartstraße Richtung Westen fahren. Dann Rechts auf Silcherstraße abbiegen.",
"verbal_post_transition_instruction": "70 Meter weiter der Route folgen.",
"street_names": [
"Vogelhartstraße"
],
"bearing_after": 273,
"time": 16.965,
"length": 0.07,
"cost": 34.428,
"begin_shape_index": 0,
"end_shape_index": 6,
"verbal_multi_cue": true,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Silcherstraße abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Silcherstraße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Silcherstraße abbiegen.",
"verbal_post_transition_instruction": "200 Meter weiter der Route folgen.",
"street_names": [
"Silcherstraße"
],
"bearing_before": 273,
"bearing_after": 5,
"time": 43.25,
"length": 0.156,
"cost": 89.306,
"begin_shape_index": 6,
"end_shape_index": 13,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Schmalkaldener Straße abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Schmalkaldener Straße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Schmalkaldener Straße abbiegen.",
"verbal_post_transition_instruction": "400 Meter weiter der Route folgen.",
"street_names": [
"Schmalkaldener Straße"
],
"bearing_before": 2,
"bearing_after": 93,
"time": 108.947,
"length": 0.43,
"cost": 217.43,
"begin_shape_index": 13,
"end_shape_index": 29,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Ingolstädter Straße/B 13 abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Ingolstädter Straße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Ingolstädter Straße, B 13 abbiegen.",
"verbal_post_transition_instruction": "einen Kilometer weiter der Route folgen.",
"street_names": [
"B 13"
],
"begin_street_names": [
"Ingolstädter Straße",
"B 13"
],
"bearing_before": 88,
"bearing_after": 178,
"time": 147.528,
"length": 1.064,
"cost": 230.646,
"begin_shape_index": 29,
"end_shape_index": 65,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 19,
"instruction": "Auf die Auffahrt nach links abbiegen.",
"verbal_transition_alert_instruction": "Auf die Auffahrt nach links abbiegen.",
"verbal_pre_transition_instruction": "Auf die Auffahrt nach links abbiegen.",
"street_names": [
"Schenkendorfstraße"
],
"bearing_before": 188,
"bearing_after": 98,
"time": 61.597,
"length": 0.374,
"cost": 117.338,
"begin_shape_index": 65,
"end_shape_index": 84,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 24,
"instruction": "Links halten auf B 2R.",
"verbal_transition_alert_instruction": "Links halten auf B 2R.",
"verbal_pre_transition_instruction": "Links halten auf B 2R.",
"verbal_post_transition_instruction": "6 Kilometer weiter der Route folgen.",
"street_names": [
"B 2R"
],
"bearing_before": 117,
"bearing_after": 118,
"time": 509.658,
"length": 6.37,
"cost": 580.602,
"begin_shape_index": 84,
"end_shape_index": 240,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 20,
"instruction": "An der Ausfahrt rechts abfahren.",
"verbal_transition_alert_instruction": "An der Ausfahrt rechts abfahren.",
"verbal_pre_transition_instruction": "An der Ausfahrt rechts abfahren.",
"verbal_post_transition_instruction": "einen Kilometer weiter der Route folgen.",
"street_names": [
"Ampfingstraße"
],
"bearing_before": 191,
"bearing_after": 206,
"time": 133.661,
"length": 1.031,
"cost": 226.661,
"begin_shape_index": 240,
"end_shape_index": 280,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 10,
"instruction": "Rechts auf Anzinger Straße abbiegen.",
"verbal_transition_alert_instruction": "Rechts auf Anzinger Straße abbiegen.",
"verbal_succinct_transition_instruction": "Rechts abbiegen.",
"verbal_pre_transition_instruction": "Rechts auf Anzinger Straße abbiegen.",
"verbal_post_transition_instruction": "1.5 Kilometer weiter der Route folgen.",
"street_names": [
"Anzinger Straße"
],
"bearing_before": 182,
"bearing_after": 277,
"time": 211.637,
"length": 1.444,
"cost": 450.654,
"begin_shape_index": 280,
"end_shape_index": 334,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 15,
"instruction": "Links auf Hohenwaldeckstraße abbiegen.",
"verbal_transition_alert_instruction": "Links auf Hohenwaldeckstraße abbiegen.",
"verbal_succinct_transition_instruction": "Links abbiegen.",
"verbal_pre_transition_instruction": "Links auf Hohenwaldeckstraße abbiegen.",
"verbal_post_transition_instruction": "200 Meter weiter der Route folgen.",
"street_names": [
"Hohenwaldeckstraße"
],
"bearing_before": 249,
"bearing_after": 170,
"time": 45.365,
"length": 0.183,
"cost": 84.344,
"begin_shape_index": 334,
"end_shape_index": 342,
"travel_mode": "drive",
"travel_type": "car"
},
{
"type": 6,
"instruction": "Hohenwaldeckstr. 27 befindet sich auf der linken Seite.",
"verbal_transition_alert_instruction": "Hohenwaldeckstr. 27 befindet sich auf der linken Seite.",
"verbal_pre_transition_instruction": "Hohenwaldeckstr. 27 befindet sich auf der linken Seite.",
"bearing_before": 184,
"time": 0,
"length": 0,
"cost": 0,
"begin_shape_index": 342,
"end_shape_index": 342,
"travel_mode": "drive",
"travel_type": "car"
}
],
"summary": {
"level_changes": [
[220, -1]
],
"has_time_restrictions": false,
"has_toll": false,
"has_highway": false,
"has_ferry": false,
"min_lat": 48.116486,
"min_lon": 11.578422,
"max_lat": 48.186957,
"max_lon": 11.616382,
"time": 1278.611,
"length": 11.123,
"cost": 2031.412
},
"shape": "mk_|zA_}vaUA^MzKKhKMrKMrLEbEeLs@kGa@yV}AmIa@cJ]g@AgQc@TgTn@ak@\\}Y`@u~@NiZRss@Ekc@AcGAwE?iB@yN@mH@_L?sAOiVEiGrISbH[|s@kC`KY~Qw@dk@mBdBErH]bIa@pNk@pAGxgA}ErQw@f`@eB`AAhEOjDO~Kg@bh@cCpTcAtEUlBKtFMbk@cBpt@eDfScAlH]hHY`HTdATbBl@rAd@|Bz@xBr@|F`AzD\\l@mHHsCAeDcAkImBiLs@}Ii@wOh@a]vAu[bB{VjCmXjCuUtDoU~A}JjBmIvDmOvDgOfCiJdB}HvAsG|FwSzGaV`IgWdC_K\\cG~Pii@pUcr@dYaz@lEkMdDsPpMm]|Tqj@tQwc@jQsb@nVwm@vEmL`k@suAxHyPzFaKrBaD|AmBxCeDpC}BvDmCnEyBnDuAzFyAhDWdDW|D?~CPjFj@lHrB|QzI~O~Jb]lS`ZbR~NnIhCdBdDtBb`Azk@fhAdr@vN~I~l@|]vr@vVb]jElZw@xG{@jEw@`KiCfLiFrJ_GnPaNjJaJzJoNxMgUtU}g@d]_w@f_@ev@tO_YhRmYbHwIxG_I|NwN~AyAdTiP~b@iZ~J}GxScQ`JoIfGwGtEwFnCqDbEaG~CcFhHgMrGcNxHmR|FaQnCwIpAeEdCiIje@}}AnQil@pGySjCyIvSor@nJ{ZpHwVbQyk@zIkXlHkTrOcc@bPic@rUyk@vKoVnMaWfWed@rT_`@jQcZhBwCzCsEtCcElC}CzCiDrCiCnH{GzLwJlGwDfH_ElGeDhHwClHyBrG{BdK_CzMsBzJgAbIq@jI?nYGbSTfGDvEDnGRfWx@|i@lEvpBbUdRrBpLfAl_@jDr|Ghm@hu@jGrWrBpd@fAxG[raBgUl[{HhM}DzOeGdXiLpCuAzwAij@lEcAbF_AnEc@|DDzE[dAGrIh@|APfe@lLbc@lLpWbIbdAnYnKlBf]|Tzi@rZbl@|ZtSjJpOhG~HvB`AVvBl@hBf@vBl@`AXrJrCtZjHhRvCrKpAjAN|Gb@hLr@dLp@xYbB`CPlDNxBLlOv@n{@jE~F\\lDP|Ov@lSn@rGNrGPlHAxICnJCvJBhFBrQL~E?dC?zFJ[xIcB|_@}Add@kAvi@i@zg@AtGEd_@f@lq@lB`|@jApd@tA`l@XpFn@lHf@bFnAdMjA`HnDpQfEfSvElPfBdOvCrWjP|xANrA|@bH\\pC\\lCNnA~Hhn@dB|MnAtJrAlK`AzH~@hHvBvPj@pEl@zFx@zH^hD~BlQdEbZhF`_@rAbJ|AjK~AtKzBrNt@nFv@lFtB`OdCfQbMb_AlDnUrDvTvF|ZzIxh@jDm@zHgBhF{@lC[`L]jMr@bNj@~_@bB"
}
],
"summary": {
"has_time_restrictions": false,
"has_toll": false,
"has_highway": false,
"has_ferry": false,
"min_lat": 48.116486,
"min_lon": 11.578422,
"max_lat": 48.186957,
"max_lon": 11.616382,
"time": 1278.611,
"length": 11.123,
"cost": 2031.412
},
"status_message": "Found route between points",
"status": 0,
"units": "kilometers",
"language": "de-DE"
},
"id": "my_work_route"
}