LocationPuck
This commit is contained in:
@@ -18,7 +18,6 @@ package com.kouros.navigation
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
@@ -34,23 +33,37 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SegmentedButtonDefaults.Icon
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import com.example.places.ui.theme.PlacesTheme
|
||||
@@ -59,10 +72,12 @@ import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import com.kouros.navigation.data.Category
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.Constants.homeLocation
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
@@ -85,18 +100,24 @@ import org.maplibre.spatialk.geojson.Position
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
val geojson = MutableLiveData("")
|
||||
val routeData = MutableLiveData("")
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
val vieModel = ViewModel(NavigationRepository())
|
||||
val routeModel = RouteModel()
|
||||
|
||||
var tilt = 0.0
|
||||
|
||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
|
||||
val instruction: MutableLiveData<StepData> by lazy {
|
||||
MutableLiveData<StepData>()
|
||||
}
|
||||
|
||||
val observer = Observer<String> { newRoute ->
|
||||
routeModel.createNavigationRoute(newRoute)
|
||||
geojson.value = routeModel.geoJson
|
||||
homeLocation.latitude = 48.155782
|
||||
homeLocation.longitude = 11.607921
|
||||
routeModel.startNavigation(newRoute)
|
||||
routeData.value = routeModel.route
|
||||
}
|
||||
|
||||
val cameraPosition = MutableLiveData(
|
||||
@@ -108,38 +129,19 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
init {
|
||||
vieModel.route.observe(this, observer)
|
||||
vieModel.loadRoute(
|
||||
homeLocation,
|
||||
Constants.home2Location
|
||||
)
|
||||
}
|
||||
|
||||
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
|
||||
updateLocation(location)
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun requestLocationUpdates() {
|
||||
val locationManager =
|
||||
getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||
updateLocation(location)
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
/* minTimeMs= */ 100,
|
||||
/* minDistanceM= */ 0f,
|
||||
mLocationListener
|
||||
)
|
||||
}
|
||||
|
||||
fun updateLocation(location: Location?) {
|
||||
fun updateLocation(location: org.maplibre.compose.location.Location?) {
|
||||
if (location != null) {
|
||||
if (routeModel.isNavigating()) {
|
||||
instruction.value = routeModel.currentStep()
|
||||
}
|
||||
val zoom = NavigationUtils().calculateZoom(location.speed)
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
zoom = 15.0,
|
||||
target = Position(location.longitude, location.latitude),
|
||||
)
|
||||
zoom = zoom,
|
||||
target = location.position
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -164,23 +166,64 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
PlacesTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Column(modifier = Modifier.padding(innerPadding)) {
|
||||
CheckPermission()
|
||||
ModalNavigationDrawer(
|
||||
drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
Text("Drawer title", modifier = Modifier.padding(16.dp))
|
||||
HorizontalDivider()
|
||||
NavigationDrawerItem(
|
||||
label = { Text(text = "Drawer Item") },
|
||||
selected = false,
|
||||
onClick = { /*TODO*/ }
|
||||
)
|
||||
}
|
||||
},
|
||||
gesturesEnabled = false
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
Text("Navigate")
|
||||
},
|
||||
icon = { Icon(true) },
|
||||
onClick = {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("Starte Navigation")
|
||||
}
|
||||
if (!routeModel.isNavigating()) {
|
||||
tilt = 60.0
|
||||
vieModel.loadRoute(
|
||||
curLocation,
|
||||
Constants.home2Location
|
||||
)
|
||||
} else {
|
||||
tilt = 0.0
|
||||
routeModel.stopNavigation()
|
||||
routeData.value = ""
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(modifier = Modifier.padding(innerPadding)) {
|
||||
CheckPermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
val locationManager =
|
||||
getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
locationManager.removeUpdates(mLocationListener)
|
||||
}
|
||||
|
||||
@SuppressLint("PermissionLaunchedDuringComposition")
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
@@ -224,11 +267,40 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationInfo(step: StepData?) {
|
||||
Card {
|
||||
Column {
|
||||
Icon(
|
||||
painter = painterResource(com.kouros.android.cars.carappservice.R.drawable.ic_turn_normal_right),
|
||||
contentDescription = stringResource(id = com.kouros.android.cars.carappservice.R.string.accept_action_title)
|
||||
)
|
||||
if (step != null) {
|
||||
Text(text = step.bearing.toString(), fontSize = 25.sp)
|
||||
Text(text = step.instruction, fontSize = 25.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Map() {
|
||||
requestLocationUpdates()
|
||||
val step: StepData? by instruction.observeAsState()
|
||||
Column {
|
||||
if (step != null) {
|
||||
NavigationInfo(step)
|
||||
}
|
||||
MapView()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MapView() {
|
||||
val locationProvider = rememberDefaultLocationProvider()
|
||||
val locationState = rememberUserLocationState(locationProvider)
|
||||
updateLocation(locationState.location)
|
||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||
val geoJsonData: String? by geojson.observeAsState()
|
||||
val route: String? by routeData.observeAsState()
|
||||
val cameraState =
|
||||
rememberCameraState(
|
||||
firstPosition =
|
||||
@@ -241,9 +313,10 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
)
|
||||
|
||||
val locationProvider = rememberDefaultLocationProvider()
|
||||
val locationState = rememberUserLocationState(locationProvider)
|
||||
|
||||
if (locationState.location != null) {
|
||||
curLocation.latitude = locationState.location?.position!!.latitude
|
||||
curLocation.longitude = locationState.location?.position!!.longitude
|
||||
}
|
||||
MaplibreMap(
|
||||
cameraState = cameraState,
|
||||
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
|
||||
@@ -270,7 +343,7 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
||||
RouteLayer(geoJsonData)
|
||||
RouteLayer(route)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +353,7 @@ class MainActivity : ComponentActivity() {
|
||||
bearing = position!!.bearing,
|
||||
zoom = position!!.zoom,
|
||||
target = position!!.target,
|
||||
tilt = tilt
|
||||
),
|
||||
duration = 3.seconds
|
||||
)
|
||||
@@ -287,21 +361,23 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RouteLayer(geoJsonData: String?) {
|
||||
val routes =
|
||||
rememberGeoJsonSource(GeoJsonData.JsonString(geoJsonData!!))
|
||||
LineLayer(
|
||||
id = "routes-casing",
|
||||
source = routes,
|
||||
color = const(Color.White),
|
||||
width = const(6.dp),
|
||||
)
|
||||
LineLayer(
|
||||
id = "routes",
|
||||
source = routes,
|
||||
color = const(Color.Blue),
|
||||
width = const(4.dp),
|
||||
)
|
||||
fun RouteLayer(routeData: String?) {
|
||||
if (routeData!!.isNotEmpty()) {
|
||||
val routes =
|
||||
rememberGeoJsonSource(GeoJsonData.JsonString(routeData!!))
|
||||
LineLayer(
|
||||
id = "routes-casing",
|
||||
source = routes,
|
||||
color = const(Color.White),
|
||||
width = const(6.dp),
|
||||
)
|
||||
LineLayer(
|
||||
id = "routes",
|
||||
source = routes,
|
||||
color = const(Color.Blue),
|
||||
width = const(4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.ScreenManager
|
||||
import androidx.car.app.Session
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
@@ -25,19 +24,18 @@ import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||
import com.kouros.navigation.car.screen.SearchScreen
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
|
||||
class NavigationSession : Session() {
|
||||
val uriScheme = "samples";
|
||||
|
||||
val uriHost = "navigation";
|
||||
|
||||
lateinit var route: RouteCarModel;
|
||||
lateinit var routeModel: RouteCarModel;
|
||||
lateinit var navigationScreen: NavigationScreen
|
||||
lateinit var surfaceRenderer: SurfaceRenderer
|
||||
var locationIndex = 0
|
||||
|
||||
val test = true
|
||||
val test = false
|
||||
|
||||
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
|
||||
updateLocation(location)
|
||||
@@ -75,12 +73,12 @@ class NavigationSession : Session() {
|
||||
}
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
route = RouteCarModel()
|
||||
routeModel = RouteCarModel()
|
||||
ObjectBox.init(carContext);
|
||||
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, route)
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
|
||||
|
||||
navigationScreen = NavigationScreen(carContext, surfaceRenderer, route)
|
||||
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel)
|
||||
|
||||
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED
|
||||
@@ -152,7 +150,7 @@ class NavigationSession : Session() {
|
||||
updateLocation(location)
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
/* minTimeMs= */ 100,
|
||||
/* minTimeMs= */ 1000,
|
||||
/* minDistanceM= */ 0f,
|
||||
mLocationListener
|
||||
)
|
||||
@@ -169,14 +167,14 @@ class NavigationSession : Session() {
|
||||
}
|
||||
|
||||
fun test(location: Location?) {
|
||||
if (route.isNavigating() && locationIndex < route.polylineLocations.size) {
|
||||
val loc = route.polylineLocations[locationIndex]
|
||||
if (routeModel.isNavigating() && locationIndex < routeModel.polylineLocations.size) {
|
||||
val loc = routeModel.polylineLocations[locationIndex]
|
||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
curLocation.longitude = loc[0]
|
||||
curLocation.latitude = loc[1]
|
||||
update(curLocation)
|
||||
locationIndex += 1
|
||||
if (locationIndex > route.polylineLocations.size) {
|
||||
if (locationIndex > routeModel.polylineLocations.size) {
|
||||
val locationManager =
|
||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
locationManager.removeUpdates(mLocationListener)
|
||||
@@ -188,12 +186,12 @@ class NavigationSession : Session() {
|
||||
|
||||
fun update(location: Location) {
|
||||
surfaceRenderer.updateLocation(location)
|
||||
if (route.isNavigating()) {
|
||||
route.findManeuver(location)
|
||||
// if (routingModel.distanceToRoute > 50) {
|
||||
// routingModel.stopNavigating()
|
||||
if (routeModel.isNavigating()) {
|
||||
routeModel.findManeuver(location)
|
||||
// if (routeModel.distanceToRoute > 50) {
|
||||
// routeModel.stopNavigation()
|
||||
// locationIndex = 0
|
||||
// surfaceRenderer.setGeoJson()
|
||||
// surfaceRenderer.setRouteData()
|
||||
// navigationScreen.reRoute()
|
||||
// }
|
||||
navigationScreen.updateTrip()
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -27,11 +28,11 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
import org.maplibre.compose.expressions.dsl.const
|
||||
import org.maplibre.compose.layers.CircleLayer
|
||||
import org.maplibre.compose.layers.FillLayer
|
||||
import org.maplibre.compose.layers.LineLayer
|
||||
import org.maplibre.compose.location.LocationPuck
|
||||
@@ -45,13 +46,13 @@ import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
||||
|
||||
class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
private var routeModel: RouteCarModel) : DefaultLifecycleObserver {
|
||||
var mVisibleArea: Rect? = null
|
||||
var mStableArea: Rect? = null
|
||||
class SurfaceRenderer(
|
||||
carContext: CarContext, lifecycle: Lifecycle,
|
||||
private var routeModel: RouteCarModel
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
private val mCarContext: CarContext = carContext
|
||||
var lastLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
val cameraPosition = MutableLiveData(
|
||||
@@ -60,15 +61,15 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
target = Position(latitude = 48.1857475, longitude = 11.5793627)
|
||||
)
|
||||
)
|
||||
val geojson = MutableLiveData("")
|
||||
val routeData = MutableLiveData("")
|
||||
|
||||
val previewGeojson = MutableLiveData("")
|
||||
val previewRouteData = MutableLiveData("")
|
||||
|
||||
var preview = false
|
||||
|
||||
lateinit var mapView: ComposeView
|
||||
|
||||
val tilt = 60.0
|
||||
val tilt = 55.0
|
||||
val padding = PaddingValues(start = 150.dp, top = 250.dp)
|
||||
|
||||
val prePadding = PaddingValues(start = 150.dp, bottom = 300.dp)
|
||||
@@ -114,15 +115,14 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
presentation.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVisibleAreaChanged(visibleArea: Rect) {
|
||||
synchronized(this@SurfaceRenderer) {
|
||||
mVisibleArea = visibleArea
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStableAreaChanged(stableArea: Rect) {
|
||||
synchronized(this@SurfaceRenderer) {
|
||||
mStableArea = stableArea
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +155,9 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
|
||||
@Composable
|
||||
fun Map() {
|
||||
|
||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||
val geoJsonData: String? by geojson.observeAsState()
|
||||
val previewGeoJsonData: String? by previewGeojson.observeAsState()
|
||||
val route: String? by routeData.observeAsState()
|
||||
val previewRoute: String? by previewRouteData.observeAsState()
|
||||
val cameraState =
|
||||
rememberCameraState(
|
||||
firstPosition =
|
||||
@@ -172,15 +171,17 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
padding = getPaddingValues()
|
||||
)
|
||||
)
|
||||
val variant = if (isSystemInDarkTheme()) "dark" else "light"
|
||||
val locationProvider = rememberDefaultLocationProvider()
|
||||
val locationState = rememberUserLocationState(locationProvider)
|
||||
|
||||
MaplibreMap(
|
||||
cameraState = cameraState,
|
||||
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
|
||||
baseStyle = BaseStyle.Uri("https://kouros-online.de/liberty"),
|
||||
) {
|
||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
||||
RouteLayer(route, previewRoute)
|
||||
}
|
||||
LocationPuck(
|
||||
idPrefix = "user-location",
|
||||
locationState = locationState,
|
||||
@@ -188,18 +189,6 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
accuracyThreshold = 10f,
|
||||
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
|
||||
)
|
||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
||||
val coordinates = mutableListOf<List<Double>>()
|
||||
coordinates.add(listOf(position!!.target.longitude, position!!.target.latitude))
|
||||
coordinates.add(
|
||||
listOf(
|
||||
position!!.target.longitude + 0.00001,
|
||||
position!!.target.latitude + 0.00001
|
||||
)
|
||||
)
|
||||
RouteLayer(geoJsonData, previewGeoJsonData)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(position) {
|
||||
@@ -217,26 +206,26 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RouteLayer(geoJsonData: String?, previewGeoJsonData: String?) {
|
||||
if (geoJsonData!!.isNotEmpty()) {
|
||||
fun RouteLayer(routeData: String?, previewRoute: String?) {
|
||||
if (routeData!!.isNotEmpty()) {
|
||||
val routes =
|
||||
rememberGeoJsonSource(GeoJsonData.JsonString(geoJsonData!!))
|
||||
rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
||||
LineLayer(
|
||||
id = "routes-casing",
|
||||
source = routes,
|
||||
color = const(Color.White),
|
||||
width = const(12.dp),
|
||||
width = const(16.dp),
|
||||
)
|
||||
LineLayer(
|
||||
id = "routes",
|
||||
source = routes,
|
||||
color = const(Color.Blue),
|
||||
width = const(10.dp),
|
||||
width = const(14.dp),
|
||||
)
|
||||
}
|
||||
if (previewGeoJsonData!!.isNotEmpty()) {
|
||||
if (previewRoute!!.isNotEmpty()) {
|
||||
val routes =
|
||||
rememberGeoJsonSource(GeoJsonData.JsonString(previewGeoJsonData!!))
|
||||
rememberGeoJsonSource(GeoJsonData.JsonString(previewRoute))
|
||||
LineLayer(
|
||||
id = "routes-casing-pre",
|
||||
source = routes,
|
||||
@@ -251,6 +240,7 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
Log.i(TAG, "SurfaceRenderer created")
|
||||
mCarContext.getCarService(AppManager::class.java)
|
||||
@@ -276,16 +266,22 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
|
||||
fun updateLocation(location: Location) {
|
||||
synchronized(this) {
|
||||
var bearing = cameraPosition.value!!.bearing
|
||||
if (lastLocation.latitude != location.latitude) {
|
||||
if (lastLocation.distanceTo(location) > 10) {
|
||||
bearing = lastLocation.bearingTo(location).toDouble()
|
||||
var bearing: Double
|
||||
if (routeModel.isNavigating()) {
|
||||
bearing = routeModel.currentStep().bearing
|
||||
} else {
|
||||
bearing = cameraPosition.value!!.bearing
|
||||
if (lastLocation.latitude != location.latitude) {
|
||||
if (lastLocation.distanceTo(location) > 5) {
|
||||
bearing = lastLocation.bearingTo(location).toDouble()
|
||||
}
|
||||
}
|
||||
}
|
||||
var zoom = NavigationUtils().calculateZoom(location.speed.toDouble())
|
||||
if (preview) {
|
||||
bearing = 0.0
|
||||
zoom = 11.0
|
||||
}
|
||||
val zoom = calculateZoom(location)
|
||||
cameraPosition.postValue(
|
||||
cameraPosition.value!!.copy(
|
||||
bearing = bearing,
|
||||
@@ -298,30 +294,13 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateZoom(location: Location): Double {
|
||||
if (preview) {
|
||||
return 11.0
|
||||
}
|
||||
//var zoom = cameraPosition.value!!.zoom
|
||||
val zoom = when (location.speed.toInt()) {
|
||||
in 0..10 -> 17.0
|
||||
in 11..20 -> 16.0
|
||||
in 21..30 -> 15.0
|
||||
in 31..40 -> 14.0
|
||||
in 41..50 -> 13.0
|
||||
in 51..60 -> 12.0
|
||||
else -> 11
|
||||
}
|
||||
return zoom.toDouble()
|
||||
}
|
||||
|
||||
fun setGeoJson() {
|
||||
geojson.value = routeModel.geoJson
|
||||
fun setRouteData() {
|
||||
routeData.value = routeModel.route
|
||||
preview = false
|
||||
}
|
||||
|
||||
fun setPreviewGeoJson(geoRoute: String) {
|
||||
previewGeojson.value = geoRoute
|
||||
fun setPreviewRouteData(route: String) {
|
||||
previewRouteData.value = route
|
||||
preview = true
|
||||
}
|
||||
|
||||
@@ -332,6 +311,7 @@ class SurfaceRenderer(carContext: CarContext, lifecycle: Lifecycle,
|
||||
padding
|
||||
}
|
||||
}
|
||||
|
||||
companion
|
||||
object {
|
||||
private const val TAG = "MapRenderer"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.kouros.navigation.car.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.SpannableString
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
@@ -43,34 +42,27 @@ class RouteCarModel() : RouteModel() {
|
||||
fun currentStep(carContext: CarContext): Step {
|
||||
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
|
||||
val maneuverType = maneuver.getInt("type")
|
||||
val distanceStepLeft = leftStepDistance() * 1000
|
||||
var text = ""
|
||||
|
||||
val stepData = currentStep()
|
||||
|
||||
var routing: (Pair<Int, CarIcon>)
|
||||
routing = if (hasArrived(maneuverType)) {
|
||||
routingData(maneuverType, carContext)
|
||||
} else {
|
||||
routingData(ManeuverType.None.value, carContext)
|
||||
}
|
||||
when (distanceStepLeft) {
|
||||
when (stepData.leftDistance) {
|
||||
in 0.0..100.0 -> {
|
||||
if (maneuverIndex < maneuvers.length()) {
|
||||
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
|
||||
if (maneuver.optJSONArray("street_names") != null) {
|
||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
||||
}
|
||||
val maneuverType = maneuver.getInt("type")
|
||||
routing = routingData(maneuverType, carContext)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (maneuver.optJSONArray("street_names") != null) {
|
||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val currentStepCueWithImage: SpannableString =
|
||||
createString(text)
|
||||
createString(stepData.instruction)
|
||||
val step =
|
||||
Step.Builder(currentStepCueWithImage)
|
||||
.setManeuver(
|
||||
@@ -88,8 +80,8 @@ class RouteCarModel() : RouteModel() {
|
||||
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
|
||||
val maneuverType = maneuver.getInt("type")
|
||||
val routing = routingData(maneuverType, carContext)
|
||||
val distanceLeft = leftStepDistance() * 1000
|
||||
var text = ""
|
||||
val distanceLeft = leftStepDistance() * 1000
|
||||
|
||||
when (distanceLeft) {
|
||||
in 0.0..100.0 -> {
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.lifecycle.Observer
|
||||
import com.kouros.android.cars.carappservice.R
|
||||
import com.kouros.navigation.car.NavigationCarAppService
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.navigation.NavigationMessage
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.Place
|
||||
@@ -41,8 +40,8 @@ class NavigationScreen(
|
||||
val vieModel = ViewModel(NavigationRepository())
|
||||
val observer = Observer<String> { route ->
|
||||
if (route.isNotEmpty()) {
|
||||
routeModel.createNavigationRoute(route)
|
||||
surfaceRenderer.setGeoJson()
|
||||
routeModel.startNavigation(route)
|
||||
surfaceRenderer.setRouteData()
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
@@ -84,8 +83,8 @@ class NavigationScreen(
|
||||
.build()
|
||||
)
|
||||
.setOnClickListener {
|
||||
surfaceRenderer.geojson.postValue("")
|
||||
routeModel.stopNavigating()
|
||||
surfaceRenderer.routeData.postValue("")
|
||||
routeModel.stopNavigation()
|
||||
invalidate()
|
||||
}
|
||||
.build()
|
||||
@@ -241,7 +240,7 @@ class NavigationScreen(
|
||||
fun updateTrip() {
|
||||
if (routeModel.maneuverType == Maneuver.TYPE_DESTINATION && routeModel.leftStepDistance() * 1000 < 25.0) {
|
||||
routeModel.arrived = true
|
||||
routeModel.stopNavigating()
|
||||
routeModel.stopNavigation()
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ class RoutePreviewScreen(
|
||||
val navigationMessage = NavigationMessage(carContext)
|
||||
val observer = Observer<String> { route ->
|
||||
if (route.isNotEmpty()) {
|
||||
routeModel.createNavigationRoute(route)
|
||||
surfaceRenderer.setPreviewGeoJson(routeModel.geoJson)
|
||||
routeModel.startNavigation(route)
|
||||
surfaceRenderer.setPreviewRouteData(routeModel.route)
|
||||
val geocoder = Geocoder(carContext)
|
||||
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
|
||||
for (address in it) {
|
||||
|
||||
@@ -47,26 +47,17 @@
|
||||
<string name="no_action_title" msgid="1452124604210014010">"Nein"</string>
|
||||
<string name="disable_all_rows" msgid="3003225080532928046">"Alle Zeilen deaktivieren"</string>
|
||||
<string name="enable_all_rows" msgid="7274285275711872091">"Alle Zeilen aktivieren"</string>
|
||||
<string name="bug_reported_toast_msg" msgid="2487119172744644317">"Fehler wurde gemeldet!"</string>
|
||||
<string name="zoomed_in_toast_msg" msgid="8915301497303842649">"Herangezoomt"</string>
|
||||
<string name="zoomed_out_toast_msg" msgid="6260981223227212493">"Herausgezoomt"</string>
|
||||
<string name="triggered_toast_msg" msgid="3396166539208366382">"Ausgelöst"</string>
|
||||
<string name="primary_toast_msg" msgid="7153771322662005447">"Primäre Schaltfläche gedrückt"</string>
|
||||
<string name="search_toast_msg" msgid="7826530065407699347">"Schaltfläche „Suchen“ gedrückt"</string>
|
||||
<string name="options_toast_msg" msgid="2146223786877557730">"Optionsschaltfläche gedrückt"</string>
|
||||
<string name="favorite_toast_msg" msgid="522064494016370117">"Favorit!"</string>
|
||||
<string name="not_favorite_toast_msg" msgid="6831181108681007428">"Kein Favorit!"</string>
|
||||
<string name="nav_requested_toast_msg" msgid="6696525973145493908">"Navigation angefragt"</string>
|
||||
<string name="selected_route_toast_msg" msgid="3149189677200086656">"Ausgewählte Route"</string>
|
||||
<string name="visible_routes_toast_msg" msgid="7065558153736024203">"Sichtbare Routen"</string>
|
||||
<string name="second_item_toast_msg" msgid="7210054709419608215">"Zweiter Eintrag angeklickt"</string>
|
||||
<string name="third_item_checked_toast_msg" msgid="3022450599567347361">"Dritter Eintrag aktiviert"</string>
|
||||
<string name="fifth_item_checked_toast_msg" msgid="1627599668504718594">"Fünfter Eintrag aktiviert"</string>
|
||||
<string name="sixth_item_toast_msg" msgid="6117028866385793707">"Sechster Eintrag angeklickt"</string>
|
||||
<string name="settings_toast_msg" msgid="7697794473002342727">"Einstellungen angeklickt"</string>
|
||||
<string name="parked_toast_msg" msgid="2532422265890824446">"Aktion „Geparkt“"</string>
|
||||
<string name="more_toast_msg" msgid="5938288138225509885">"„Mehr“ angeklickt"</string>
|
||||
<string name="commute_toast_msg" msgid="4112684360647638688">"Schaltfläche für Arbeitsweg gedrückt"</string>
|
||||
<string name="grant_location_permission_toast_msg" msgid="268046297444808010">"Standortermittlung erlauben, um aktuellen Standort anzuzeigen"</string>
|
||||
<string name="sign_in_with_google_toast_msg" msgid="5720947549233124775">"Über Google anmelden beginnt hier"</string>
|
||||
<string name="changes_selection_to_index_toast_msg_prefix" msgid="957766225794389167">"Auswahl auf Index geändert"</string>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Showcase</string>
|
||||
<string name="app_name" translatable="false">Navigation</string>
|
||||
|
||||
<!-- Action Titles -->
|
||||
<string name="back_caps_action_title">BACK</string>
|
||||
@@ -50,27 +50,18 @@
|
||||
<string name="enable_all_rows">Enable All Rows</string>
|
||||
|
||||
<!-- Toast Messages -->
|
||||
<string name="bug_reported_toast_msg">Bug reported!</string>
|
||||
<string name="zoomed_in_toast_msg">Zoomed in</string>
|
||||
<string name="zoomed_in_toast_msg">Zoomed in</string>
|
||||
<string name="zoomed_out_toast_msg">Zoomed out</string>
|
||||
<string name="triggered_toast_msg">Triggered</string>
|
||||
<string name="primary_toast_msg">Primary button pressed</string>
|
||||
<string name="search_toast_msg">Search button pressed</string>
|
||||
<string name="options_toast_msg">Options button pressed</string>
|
||||
<string name="favorite_toast_msg">Favorite!</string>
|
||||
<string name="favorite_toast_msg">Favorite!</string>
|
||||
<string name="not_favorite_toast_msg">Not a favorite!</string>
|
||||
<string name="nav_requested_toast_msg">Navigation Requested</string>
|
||||
<string name="selected_route_toast_msg">Selected route</string>
|
||||
<string name="visible_routes_toast_msg">Visible routes</string>
|
||||
<string name="second_item_toast_msg">Clicked second item</string>
|
||||
<string name="third_item_checked_toast_msg">Third item checked</string>
|
||||
<string name="fifth_item_checked_toast_msg">Fifth item checked</string>
|
||||
<string name="sixth_item_toast_msg">Clicked sixth item</string>
|
||||
<string name="settings_toast_msg">Clicked Settings</string>
|
||||
<string name="settings_toast_msg">Clicked Settings</string>
|
||||
<string name="parked_toast_msg">Parked action</string>
|
||||
<string name="more_toast_msg">Clicked More</string>
|
||||
<string name="commute_toast_msg">Commute button pressed</string>
|
||||
<string name="grant_location_permission_toast_msg">Grant location Permission to see current location</string>
|
||||
<string name="grant_location_permission_toast_msg">Grant location Permission to see current location</string>
|
||||
<string name="sign_in_with_google_toast_msg">Sign-in with Google starts here</string>
|
||||
<string name="changes_selection_to_index_toast_msg_prefix">Changed selection to index</string>
|
||||
<string name="yes_action_toast_msg">Yes button pressed!</string>
|
||||
|
||||
255
common/car/src/test/java/com/kouros/navigation/car/Route.json
Normal file
255
common/car/src/test/java/com/kouros/navigation/car/Route.json
Normal file
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import com.kouros.navigation.car.navigation.RoutingModel
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import com.kouros.navigation.data.Constants.home2Location
|
||||
import com.kouros.navigation.data.Constants.homeLocation
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
@@ -14,5 +20,18 @@ class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
val model = RouteModel()
|
||||
val repo = NavigationRepository()
|
||||
val viewModel = ViewModel(repo)
|
||||
val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
fromLocation.latitude = homeLocation.latitude
|
||||
fromLocation.longitude = homeLocation.longitude
|
||||
val toLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
toLocation.latitude = home2Location.latitude
|
||||
toLocation.longitude = home2Location.longitude
|
||||
|
||||
val route = repo.getRoute(fromLocation, toLocation)
|
||||
model.startNavigation(route)
|
||||
println(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import io.objectbox.annotation.Entity
|
||||
import io.objectbox.annotation.Id
|
||||
import io.objectbox.annotation.Index
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
data class Category(
|
||||
@@ -52,6 +51,12 @@ data class ContactData(
|
||||
val avatar: Uri?
|
||||
)
|
||||
|
||||
data class StepData (
|
||||
var instruction: String,
|
||||
var leftDistance: Double,
|
||||
var bearing: Double
|
||||
)
|
||||
|
||||
|
||||
//val places = mutableListOf<Place>()
|
||||
/* Place(
|
||||
@@ -33,7 +33,9 @@ class Contacts(private var context: Context) {
|
||||
while (moveToNext()) {
|
||||
val contactId = getLong(getColumnIndex(ContactsContract.Data.CONTACT_ID))
|
||||
val name = getString(getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
|
||||
if (name.contains("Jola") || name.contains("Dominic")
|
||||
if (name.contains("Jola")
|
||||
|| name.contains("Dominic")
|
||||
|| name.contains("Martha")
|
||||
|| name.contains("Μεντή")
|
||||
|| name.contains("David")) {
|
||||
val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE))
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.model
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
|
||||
import com.kouros.navigation.utils.NavigationUtils.Utils.decodePolyline
|
||||
import org.json.JSONArray
|
||||
@@ -10,23 +11,19 @@ import org.json.JSONObject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
open class RouteModel {
|
||||
|
||||
// Source - https://stackoverflow.com/a
|
||||
// Posted by Dmitrii Bychkov
|
||||
// Retrieved 2025-11-14, License - CC BY-SA 4.0
|
||||
open class RouteModel () {
|
||||
var polylineLocations: List<List<Double>> = emptyList()
|
||||
|
||||
lateinit var maneuvers: JSONArray
|
||||
lateinit var locations: JSONArray
|
||||
lateinit var summary: JSONObject
|
||||
|
||||
lateinit var destination: Place
|
||||
var navigating = false
|
||||
|
||||
var arrived = false
|
||||
|
||||
var maneuverIndex = 0
|
||||
|
||||
var maneuverType = 0
|
||||
|
||||
var currentIndex = 0
|
||||
|
||||
var distanceToStepEnd = 0F
|
||||
@@ -38,13 +35,23 @@ open class RouteModel {
|
||||
|
||||
var distanceToRoute = 0F
|
||||
|
||||
var geoJson = ""
|
||||
var route = ""
|
||||
|
||||
private fun decodeValhallaRoute(route: String) {
|
||||
if (route.isEmpty() || route == "[]") {
|
||||
data class Builder(
|
||||
var route: String? = null,
|
||||
var fromLocation: Location? = null,
|
||||
var toLocation: Location? = null) {
|
||||
|
||||
fun route(route: String) = apply { this.route = route }
|
||||
fun fromLocation(fromLocation: Location) = apply { this.fromLocation = fromLocation }
|
||||
fun toLocation(toLocation: Location) = apply { this.toLocation = toLocation }
|
||||
//fun build() = RouteModel(route!!, fromLocation!!, toLocation!!)
|
||||
}
|
||||
private fun decodeValhallaRoute(valhallaRoute: String) {
|
||||
if (valhallaRoute.isEmpty() || valhallaRoute == "[]") {
|
||||
return;
|
||||
}
|
||||
val jObject = JSONObject(route)
|
||||
val jObject = JSONObject(valhallaRoute)
|
||||
val trip = jObject.getJSONObject("trip")
|
||||
locations = trip.getJSONArray("locations")
|
||||
val legs = trip.getJSONArray("legs")
|
||||
@@ -54,13 +61,13 @@ open class RouteModel {
|
||||
polylineLocations = decodePolyline(shape)
|
||||
}
|
||||
|
||||
fun createNavigationRoute(route: String) {
|
||||
decodeValhallaRoute(route)
|
||||
fun startNavigation(valhallaRoute: String) {
|
||||
decodeValhallaRoute(valhallaRoute)
|
||||
for (i in 0..<maneuvers.length()) {
|
||||
val maneuver = (maneuvers[i] as JSONObject)
|
||||
routingManeuvers.add(maneuver)
|
||||
}
|
||||
geoJson = createGeoJson(polylineLocations)
|
||||
route = createGeoJson(polylineLocations)
|
||||
navigating = true
|
||||
}
|
||||
|
||||
@@ -88,6 +95,31 @@ open class RouteModel {
|
||||
distanceToRoute = nearestDistance
|
||||
}
|
||||
|
||||
fun currentStep(): StepData {
|
||||
var bearing = 0
|
||||
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
|
||||
var text = ""
|
||||
if (maneuver.optJSONArray("street_names") != null) {
|
||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
||||
}
|
||||
if (maneuver.has("bearing_after")) {
|
||||
bearing = maneuver.getInt("bearing_after")
|
||||
}
|
||||
val distanceStepLeft = leftStepDistance() * 1000
|
||||
when (distanceStepLeft) {
|
||||
in 0.0..100.0 -> {
|
||||
if (maneuverIndex < maneuvers.length()) {
|
||||
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
|
||||
if (maneuver.optJSONArray("street_names") != null) {
|
||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return StepData(text, distanceStepLeft, bearing.toDouble())
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Calculates the index in a maneuver. */
|
||||
private fun calculateCurrentIndex(
|
||||
@@ -159,8 +191,6 @@ open class RouteModel {
|
||||
val maneuver = routingManeuvers[maneuverIndex]
|
||||
var leftDistance = maneuver.getDouble("length")
|
||||
if (endIndex > 0) {
|
||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
||||
//leftDistance = leftDistance * percent / 100
|
||||
leftDistance = (distanceToStepEnd / 1000).toDouble()
|
||||
}
|
||||
return leftDistance
|
||||
@@ -190,11 +220,11 @@ open class RouteModel {
|
||||
return arrived
|
||||
}
|
||||
|
||||
fun stopNavigating() {
|
||||
fun stopNavigation() {
|
||||
navigating = false
|
||||
polylineLocations = mutableListOf()
|
||||
routingManeuvers = mutableListOf()
|
||||
geoJson = ""
|
||||
route = ""
|
||||
maneuverIndex = 0
|
||||
currentIndex = 0
|
||||
distanceToStepEnd = 0F
|
||||
|
||||
@@ -105,4 +105,20 @@ class NavigationUtils() {
|
||||
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateZoom(speed: Double?): Double {
|
||||
if (speed == null) {
|
||||
return 18.0
|
||||
}
|
||||
val zoom = when (speed.toInt()) {
|
||||
in 0..10 -> 17.0
|
||||
in 11..20 -> 17.0
|
||||
in 21..30 -> 17.0
|
||||
in 31..40 -> 16.0
|
||||
in 41..50 -> 15.0
|
||||
in 51..60 -> 14.0
|
||||
else -> 11
|
||||
}
|
||||
return zoom.toDouble()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ ui = "1.9.4"
|
||||
material3 = "1.4.0"
|
||||
runtimeLivedata = "1.9.4"
|
||||
foundation = "1.9.4"
|
||||
maplibre-composeMaterial3 = "0.12.2"
|
||||
maplibre-compose = "0.12.1"
|
||||
playServicesLocation = "21.3.0"
|
||||
runtime = "1.9.4"
|
||||
@@ -38,7 +39,7 @@ androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "ca
|
||||
#objectbox-kotlin = { module = "io.objectbox:objectbox-kotlin", version.ref = "objectboxKotlin" }
|
||||
ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
|
||||
maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre-compose" }
|
||||
maplibre-composeMaterial3 = { module = "org.maplibre.compose:maplibre-compose-material3", version = "maplibre-compose" }
|
||||
maplibre-composeMaterial3 = { module = "org.maplibre.compose:maplibre-compose-material3", version = "maplibre-composeMaterial3" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
|
||||
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
|
||||
|
||||
Reference in New Issue
Block a user