LocationPuck

This commit is contained in:
Dimitris
2025-11-15 12:38:40 +01:00
parent d63747e811
commit 1773ec2244
15 changed files with 568 additions and 213 deletions

View File

@@ -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