Serialize Json

This commit is contained in:
Dimitris
2025-11-20 10:27:33 +01:00
parent 3f3bdeb96d
commit 33f5ef4f34
24 changed files with 919 additions and 391 deletions

View File

@@ -1,19 +1,3 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kouros.navigation
import android.Manifest
@@ -21,7 +5,6 @@ import android.annotation.SuppressLint
import android.location.Location
import android.location.LocationManager
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -47,7 +30,7 @@ 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
@@ -68,14 +51,16 @@ import androidx.lifecycle.Observer
import com.example.places.ui.theme.PlacesTheme
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.TAG
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 com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import org.maplibre.compose.camera.CameraPosition
@@ -83,14 +68,14 @@ import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.location.LocationTrackingEffect
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.map.GestureOptions
import org.maplibre.compose.map.MapOptions
import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.map.OrnamentOptions
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
@@ -99,24 +84,24 @@ import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
val routeData = MutableLiveData("")
class MainActivity : ComponentActivity() {
val routeData = MutableLiveData("")
val vieModel = ViewModel(NavigationRepository())
val routeModel = RouteModel()
var tilt = 0.0
val curLocation = Location(LocationManager.GPS_PROVIDER)
var tilt = 50.0
val instruction: MutableLiveData<StepData> by lazy {
MutableLiveData<StepData>()
}
var lastLocation = Location(LocationManager.GPS_PROVIDER)
val observer = Observer<String> { newRoute ->
routeModel.startNavigation(newRoute)
routeData.value = routeModel.route
routeData.value = routeModel.route.routeGeoJson
}
val cameraPosition = MutableLiveData(
@@ -126,40 +111,14 @@ class MainActivity : ComponentActivity() {
)
)
var locationIndex = 0
var test = false
init {
vieModel.route.observe(this, observer)
}
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 = zoom,
target = location.position
),
)
}
}
fun test() {
for (i in 0..<routeModel.polylineLocations.size) {
val loc = routeModel.polylineLocations[i]
val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0]
curLocation.latitude = loc[1]
routeModel.updateLocation(curLocation)
val leftTime = routeModel.travelLeftTime()
val leftDistance = routeModel.travelLeftDistance()
Log.i(TAG, " leftTime: ${leftTime / 60}")
Log.i(TAG, " leftDistance: $leftDistance")
Log.i(TAG, "Cue: ${routeModel.maneuvers[routeModel.maneuverIndex]}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -198,10 +157,10 @@ class MainActivity : ComponentActivity() {
scope.launch {
snackbarHostState.showSnackbar("Starte Navigation")
}
if (!routeModel.isNavigating()) {
if (!routeModel.isNavigating() && lastLocation.latitude != 0.0) {
tilt = 60.0
vieModel.loadRoute(
curLocation,
lastLocation,
Constants.home2Location
)
} else {
@@ -271,8 +230,8 @@ class MainActivity : ComponentActivity() {
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)
painter = painterResource(R.drawable.ic_turn_normal_right),
contentDescription = stringResource(id = R.string.accept_action_title)
)
if (step != null) {
Text(text = step.bearing.toString(), fontSize = 25.sp)
@@ -295,10 +254,23 @@ class MainActivity : ComponentActivity() {
@Composable
fun MapView() {
val locationProvider = rememberDefaultLocationProvider()
val locationState = rememberUserLocationState(locationProvider)
updateLocation(locationState.location)
val locationProvider = rememberDefaultLocationProvider(
updateInterval = 0.1.seconds,
desiredAccuracy = DesiredAccuracy.Highest
)
val userLocationState = rememberUserLocationState(locationProvider)
val locationState = locationProvider.location.collectAsState()
if (!test) {
updateLocation(locationState.value)
} else {
test()
}
if (locationState.value != null && lastLocation.latitude == 0.0) {
lastLocation.latitude = locationState.value?.position!!.latitude
lastLocation.longitude = locationState.value?.position!!.longitude
}
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val cameraState =
rememberCameraState(
@@ -311,42 +283,32 @@ class MainActivity : ComponentActivity() {
zoom = 15.0,
)
)
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"),
baseStyle = BaseStyle.Uri("https://kouros-online.de/liberty"),
options =
MapOptions(
gestureOptions = GestureOptions(
isTiltEnabled = true,
isZoomEnabled = true,
isRotateEnabled = false,
isScrollEnabled = true,
),
ornamentOptions = OrnamentOptions(
isScaleBarEnabled = false
)
)
baseStyle = BaseStyle.Uri(Constants.STYLE),
) {
LocationPuck(
idPrefix = "user-location",
locationState = locationState,
cameraState = cameraState,
accuracyThreshold = 10f,
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
)
getBaseSource(id = "openmaptiles")?.let { tiles ->
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
RouteLayer(route)
}
LocationPuck(
idPrefix = "user-location1",
locationState = userLocationState,
cameraState = cameraState,
accuracyThreshold = 10f,
showBearing = false,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(
dotFillColorCurrentLocation = Color.Cyan,
accuracyStrokeColor = Color.Green
)
)
}
LaunchedEffect(position) {
LocationTrackingEffect(
locationState = userLocationState,
) {
//cameraState.updateFromLocation()
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = position!!.bearing,
@@ -354,9 +316,22 @@ class MainActivity : ComponentActivity() {
target = position!!.target,
tilt = tilt
),
duration = 3.seconds
duration = 1.seconds
)
}
// LaunchedEffect(position) {
// println("CameraPosition ${position!!.target.latitude}")
// cameraState.animateTo(
// finalPosition = CameraPosition(
// bearing = position!!.bearing,
// zoom = position!!.zoom,
// target = position!!.target,
// tilt = tilt
// ),
// duration = 3.seconds
// )
// }
}
@Composable
@@ -368,17 +343,66 @@ class MainActivity : ComponentActivity() {
id = "routes-casing",
source = routes,
color = const(Color.White),
width = const(6.dp),
width = const(10.dp),
)
LineLayer(
id = "routes",
source = routes,
color = const(Color.Blue),
width = const(4.dp),
width = const(8.dp),
)
}
}
fun updateLocation(location: org.maplibre.compose.location.Location?) {
if (location != null) {
if (routeModel.isNavigating()) {
routeModel.updateLocation(lastLocation)
instruction.value = routeModel.currentStep()
}
val zoom = calculateZoom(location.speed)
cameraPosition.postValue(
cameraPosition.value!!.copy(
zoom = zoom,
target = location.position
),
)
}
}
fun updateTestLocation(location: Location) {
var snapedLocation = location
var bearing: Double
if (routeModel.isNavigating()) {
snapedLocation = snapLocation(location, routeModel.maneuverLocations())
bearing = routeModel.currentStep().bearing
routeModel.updateLocation(snapedLocation)
instruction.value = routeModel.currentStep()
} else {
bearing = cameraPosition.value!!.bearing
}
val zoom = calculateZoom(snapedLocation.speed.toDouble())
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
target = Position(snapedLocation.longitude, snapedLocation.latitude)
),
)
}
fun test() {
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
val loc = routeModel.route.waypoints[locationIndex]
lastLocation.longitude = loc[0]
lastLocation.latitude = loc[1]
updateTestLocation(lastLocation)
Thread.sleep(1_000)
locationIndex++
}
}
@Composable
fun PlaceList(viewModel: ViewModel = koinViewModel()) {
var categories: List<Category>

View File

@@ -45,6 +45,7 @@ dependencies {
implementation(libs.androidx.ui)
implementation(libs.maplibre.compose)
//implementation(libs.maplibre.composeMaterial3)
implementation(project(":common:data"))
implementation(libs.androidx.runtime.livedata)
implementation(libs.androidx.compose.foundation)

View File

@@ -0,0 +1,257 @@
package com.kouros.navigation.car
import android.location.Location
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.Path
import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.VectorPainter
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.expressions.dsl.asNumber
import org.maplibre.compose.expressions.dsl.condition
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.expressions.dsl.div
import org.maplibre.compose.expressions.dsl.dp
import org.maplibre.compose.expressions.dsl.feature
import org.maplibre.compose.expressions.dsl.gt
import org.maplibre.compose.expressions.dsl.image
import org.maplibre.compose.expressions.dsl.minus
import org.maplibre.compose.expressions.dsl.offset
import org.maplibre.compose.expressions.dsl.plus
import org.maplibre.compose.expressions.dsl.switch
import org.maplibre.compose.expressions.value.IconRotationAlignment
import org.maplibre.compose.expressions.value.SymbolAnchor
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.SymbolLayer
import org.maplibre.compose.location.LocationClickHandler
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.GeoJsonSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.FeatureCollection
import org.maplibre.spatialk.geojson.Point
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@Composable
public fun LocationPuck(
idPrefix: String,
locationState: Location,
cameraState: CameraState,
oldLocationThreshold: Duration = 30.seconds,
accuracyThreshold: Float = 50f,
colors: LocationPuckColors = LocationPuckColors(),
sizes: LocationPuckSizes = LocationPuckSizes(),
showBearing: Boolean = true,
showBearingAccuracy: Boolean = true,
onClick: LocationClickHandler? = null,
onLongClick: LocationClickHandler? = null,
) {
val bearingPainter = rememberBearingPainter(sizes, colors)
val bearingAccuracyPainter =
rememberBearingAccuracyPainter(
sizes = sizes,
colors = colors,
bearingAccuracy = locationState.bearingAccuracyDegrees
)
val locationSource = rememberLocationSource(locationState)
CircleLayer(
id = "$idPrefix-accuracy",
source = locationSource,
visible =
accuracyThreshold <= Float.POSITIVE_INFINITY &&
locationState.let { it.accuracy > accuracyThreshold },
radius =
switch(
condition(
test =
feature["age"].asNumber() gt const(oldLocationThreshold.inWholeNanoseconds.toFloat()),
output = const(0.dp),
),
fallback =
(feature["accuracy"].asNumber() / const(cameraState.metersPerDpAtTarget.toFloat())).dp,
),
color = const(colors.accuracyFillColor),
strokeColor = const(colors.accuracyStrokeColor),
strokeWidth = const(sizes.accuracyStrokeWidth),
)
CircleLayer(
id = "$idPrefix-shadow",
source = locationSource,
visible = sizes.shadowSize > 0.dp,
radius = const(sizes.dotRadius + sizes.dotStrokeWidth + sizes.shadowSize),
color = const(colors.shadowColor),
blur = const(sizes.shadowBlur),
translate = const(DpOffset(0.dp, 1.dp)),
)
CircleLayer(
id = "$idPrefix-dot",
source = locationSource,
visible = true,
radius = const(sizes.dotRadius),
color =
switch(
condition(
test =
feature["age"].asNumber() gt const(oldLocationThreshold.inWholeNanoseconds.toFloat()),
output = const(colors.dotFillColorOldLocation),
),
fallback = const(colors.dotFillColorCurrentLocation),
),
strokeColor = const(colors.dotStrokeColor),
strokeWidth = const(sizes.dotStrokeWidth),
)
SymbolLayer(
id = "$idPrefix-bearing",
source = locationSource,
visible = showBearing,
iconImage = image(bearingPainter),
iconAnchor = const(SymbolAnchor.Center),
iconRotate = feature["bearing"].asNumber(const(0f)) + const(45f),
iconOffset =
offset(
-(sizes.dotRadius + sizes.dotStrokeWidth) * sqrt(2f) / 2f,
-(sizes.dotRadius + sizes.dotStrokeWidth) * sqrt(2f) / 2f,
),
iconRotationAlignment = const(IconRotationAlignment.Map),
iconAllowOverlap = const(true),
)
SymbolLayer(
id = "$idPrefix-bearingAccuracy",
source = locationSource,
visible =
showBearingAccuracy,
iconImage = image(bearingAccuracyPainter),
iconAnchor = const(SymbolAnchor.Center),
iconRotate =
feature["bearing"].asNumber(const(0f)) -
const(90f) -
feature["bearingAccuracy"].asNumber(const(0f)),
iconRotationAlignment = const(IconRotationAlignment.Map),
iconAllowOverlap = const(true),
)
}
@Composable
private fun rememberBearingPainter(
sizes: LocationPuckSizes,
colors: LocationPuckColors,
): VectorPainter {
return rememberVectorPainter(
defaultWidth = sizes.bearingSize,
defaultHeight = sizes.bearingSize,
autoMirror = false,
) { viewportWidth, viewportHeight ->
Path(
pathData =
PathData {
moveTo(0f, 0f)
lineTo(0f, viewportHeight)
lineTo(viewportWidth, 0f)
close()
},
fill = SolidColor(colors.bearingColor),
)
}
}
@Composable
private fun rememberBearingAccuracyPainter(
sizes: LocationPuckSizes,
colors: LocationPuckColors,
bearingAccuracy: Float,
): VectorPainter {
val density by rememberUpdatedState(LocalDensity.current)
val dotRadius by rememberUpdatedState(sizes.dotRadius)
val dotStrokeWidth by rememberUpdatedState(sizes.dotStrokeWidth)
val bearingColor by rememberUpdatedState(colors.bearingColor)
val bearingAccuracy by rememberUpdatedState(bearingAccuracy)
val bearingAccuracyVector by remember {
derivedStateOf {
val radius = with(density) { Offset(dotRadius.toPx(), dotRadius.toPx()) }
val deltaDegrees = 2 * bearingAccuracy
val delta = (PI * deltaDegrees / 180.0).toFloat()
val width = 2 * dotRadius + 2 * dotStrokeWidth
val height = 2 * dotRadius + 2 * dotStrokeWidth
val center = with(density) { Offset((width / 2).toPx(), (height / 2).toPx()) }
val start = center + Offset(radius.x, 0f)
val end = center + Offset(radius.x * cos(delta), radius.y * sin(delta))
ImageVector.Builder(
defaultWidth = width,
defaultHeight = height,
viewportWidth = with(density) { width.toPx() },
viewportHeight = with(density) { height.toPx() },
autoMirror = false,
)
.apply {
path(
stroke = SolidColor(bearingColor),
strokeLineWidth = with(density) { dotStrokeWidth.toPx() },
) {
moveTo(start.x, start.y)
arcTo(radius.x, radius.y, 0f, delta > PI, delta > 0, end.x, end.y)
}
}
.build()
}
}
return rememberVectorPainter(bearingAccuracyVector)
}
@Composable
private fun rememberLocationSource(locationState: Location): GeoJsonSource {
val features =
remember(locationState) {
val location = locationState
FeatureCollection(
Feature(
geometry = Point(location.longitude, location.latitude),
properties =
buildJsonObject {
put("accuracy", location.accuracy)
put("bearing", location.bearing)
//put("bearingAccuracy", location.bearingAccuracy)
//put("age", location.timestamp.elapsedNow().inWholeNanoseconds)
},
)
)
}
return rememberGeoJsonSource(GeoJsonData.Features(features))
}
public typealias LocationClickHandler = (org.maplibre.compose.location.Location) -> Unit

View File

@@ -167,14 +167,14 @@ class NavigationSession : Session() {
}
fun test(location: Location?) {
if (routeModel.isNavigating() && locationIndex < routeModel.polylineLocations.size) {
val loc = routeModel.polylineLocations[locationIndex]
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
val loc = routeModel.route.waypoints[locationIndex]
val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0]
curLocation.latitude = loc[1]
curLocation.longitude = loc[0] + 0.0003
curLocation.latitude = loc[1] + 0.0002
update(curLocation)
locationIndex += 1
if (locationIndex > routeModel.polylineLocations.size) {
if (locationIndex > routeModel.route.waypoints.size) {
val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.removeUpdates(mLocationListener)
@@ -185,16 +185,10 @@ class NavigationSession : Session() {
}
fun update(location: Location) {
surfaceRenderer.updateLocation(location)
if (routeModel.isNavigating()) {
routeModel.updateLocation(location)
// if (routeModel.distanceToRoute > 50) {
// routeModel.stopNavigation()
// locationIndex = 0
// surfaceRenderer.setRouteData()
// navigationScreen.reRoute()
// }
navigationScreen.updateTrip()
}
surfaceRenderer.updateLocation(location)
}
}

View File

@@ -26,17 +26,17 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.map.MaplibreMap
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.getBaseSource
@@ -68,6 +68,7 @@ class SurfaceRenderer(
lateinit var mapView: ComposeView
var panView = false
val tilt = 55.0
val padding = PaddingValues(start = 150.dp, top = 250.dp)
@@ -154,8 +155,6 @@ class SurfaceRenderer(
@Composable
fun MapView() {
val locationProvider = rememberDefaultLocationProvider()
val locationState = rememberUserLocationState(locationProvider)
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState()
@@ -174,18 +173,19 @@ class SurfaceRenderer(
)
MaplibreMap(
cameraState = cameraState,
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
baseStyle = BaseStyle.Uri("https://kouros-online.de/liberty"),
baseStyle = BaseStyle.Uri(Constants.STYLE),
) {
getBaseSource(id = "openmaptiles")?.let { tiles ->
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
RouteLayer(route, previewRoute)
}
LocationPuck(
idPrefix = "user-location",
locationState = locationState,
locationState = lastLocation,
cameraState = cameraState,
accuracyThreshold = 10f,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
)
}
@@ -264,6 +264,7 @@ class SurfaceRenderer(
/** Handles the map zoom-in and zoom-out events. */
fun handleScale(zoomSign: Int) {
synchronized(this) {
panView = true
val newZoom = if (zoomSign < 0) {
cameraPosition.value!!.zoom - 1.0
} else {
@@ -281,27 +282,33 @@ class SurfaceRenderer(
fun updateLocation(location: Location) {
synchronized(this) {
if (!preview) {
var snapedLocation = location
var bearing: Double
if (routeModel.isNavigating()) {
snapedLocation = snapLocation(location, routeModel.maneuverLocations())
bearing = routeModel.currentStep().bearing
} else {
bearing = cameraPosition.value!!.bearing
if (lastLocation.latitude != location.latitude) {
if (lastLocation.distanceTo(location) > 5) {
bearing = lastLocation.bearingTo(location).toDouble()
if (lastLocation.latitude != snapedLocation.latitude) {
if (lastLocation.distanceTo(snapedLocation) > 5) {
bearing = lastLocation.bearingTo(snapedLocation).toDouble()
}
}
}
val zoom = NavigationUtils().calculateZoom(location.speed.toDouble())
val zoom = if (!panView) {
calculateZoom(snapedLocation.speed.toDouble())
} else {
cameraPosition.value!!.zoom
}
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
padding = getPaddingValues(),
target = Position(location.longitude, location.latitude),
target = Position(snapedLocation.longitude, snapedLocation.latitude),
)
)
lastLocation = location
lastLocation = snapedLocation
} else {
val bearing = 0.0
val zoom = 11.0
@@ -319,12 +326,13 @@ class SurfaceRenderer(
fun setRouteData() {
routeData.value = routeModel.route
routeData.value = routeModel.route.routeGeoJson
preview = false
panView = false
}
fun setPreviewRouteData(routeModel: RouteModel) {
previewRouteData.value = routeModel.route
previewRouteData.value = routeModel.route.routeGeoJson
centerLocation = routeModel.centerLocation
preview = true
}

View File

@@ -40,8 +40,8 @@ class RouteCarModel() : RouteModel() {
/** Returns the current [Step] with information such as the cue text and images. */
fun currentStep(carContext: CarContext): Step {
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
val maneuverType = maneuver.getInt("type")
val maneuver = route.currentManeuver()
val maneuverType = maneuver.type
val stepData = currentStep()
@@ -53,9 +53,9 @@ class RouteCarModel() : RouteModel() {
}
when (stepData.leftDistance) {
in 0.0..100.0 -> {
if (maneuverIndex < maneuvers.length()) {
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
val maneuverType = maneuver.getInt("type")
if (route.currentIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
routing = routingData(maneuverType, carContext)
}
}
@@ -77,22 +77,22 @@ class RouteCarModel() : RouteModel() {
/** Returns the next [Step] with information such as the cue text and images. */
fun nextStep(carContext: CarContext): Step {
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
val maneuverType = maneuver.getInt("type")
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
val routing = routingData(maneuverType, carContext)
var text = ""
val distanceLeft = leftStepDistance() * 1000
when (distanceLeft) {
in 0.0..100.0 -> {
if (maneuver.optJSONArray("street_names") != null) {
text = maneuver.getJSONArray("street_names").get(0) as String
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
text = maneuver.streetNames!![0]
}
}
else -> {
if (maneuver.optJSONArray("street_names") != null) {
text = maneuver.getJSONArray("street_names").get(0) as String
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
text = maneuver.streetNames!![0]
}
}
}
@@ -113,40 +113,42 @@ class RouteCarModel() : RouteModel() {
var type = Maneuver.TYPE_DEPART
var currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
when (routeManeuverType) {
ManeuverType.Destination.value,
ManeuverType.DestinationLeft.value,
ManeuverType.DestinationRight.value
-> {
type = Maneuver.TYPE_DESTINATION
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_destination)
}
ManeuverType.None.value -> {
type = Maneuver.TYPE_STRAIGHT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
}
ManeuverType.Destination.value,
ManeuverType.DestinationRight.value,
ManeuverType.DestinationLeft.value,
-> {
type = Maneuver.TYPE_DESTINATION
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_destination)
}
ManeuverType.Right.value -> {
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
// currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_right)
currentTurnIcon = createCarIcon(carContext, R.drawable.turn_right_48px1)
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_right)
}
ManeuverType.Left.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_left)
}
ManeuverType.RampRight.value -> {
type = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_slight_right)
}
ManeuverType.RampLeft.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_left)
}
ManeuverType.ExitRight.value -> {
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_slight_right)
}
ManeuverType.StayRight.value -> {
type = Maneuver.TYPE_KEEP_RIGHT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
}
ManeuverType.StayLeft.value -> {
type = Maneuver.TYPE_KEEP_LEFT
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)

View File

@@ -51,7 +51,6 @@ class NavigationScreen(
}
override fun onGetTemplate(): Template {
// Log.i(TAG, "onGetTemplate NavigationScreen")
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
actionStripBuilder.addAction(
Action.Builder()

View File

@@ -4,6 +4,7 @@ import android.location.Location
import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
import android.util.Log
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
@@ -22,6 +23,7 @@ import com.kouros.android.cars.carappservice.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.TAG
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel

View File

@@ -49,7 +49,6 @@ import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel
import java.math.BigDecimal
import java.math.RoundingMode
import kotlin.math.roundToInt
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
class RoutePreviewScreen(
@@ -110,7 +109,7 @@ class RoutePreviewScreen(
val itemListBuilder = ItemList.Builder()
if (routeModel.polylineLocations.isNotEmpty()) {
if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) {
itemListBuilder.addItem(createRow(0, navigateAction))
}
@@ -201,9 +200,8 @@ class RoutePreviewScreen(
private fun createRouteText(index: Int): CarText {
val time = routeModel.routeTime
val length = BigDecimal(routeModel.routeDistance).setScale(1, RoundingMode.HALF_EVEN)
val time = routeModel.route.summary.time
val length = BigDecimal(routeModel.route.distance).setScale(1, RoundingMode.HALF_EVEN)
val firstRoute = SpannableString(" \u00b7 $length km")
firstRoute.setSpan(
DurationSpan.create(time.toLong()), 0, 1,0

View File

@@ -17,7 +17,7 @@ import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Place
import com.kouros.navigation.utils.NavigationUtils.Utils.getBoundingBox
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox
class SearchScreen(
@@ -42,8 +42,6 @@ class SearchScreen(
val searchItemListBuilder = ItemList.Builder()
.setNoItemsMessage("No search results to show")
println("OnGetTemplate SearchScreen ${categories.size}")
if (!isSearching) {
categories.forEach {
it.name
@@ -111,7 +109,7 @@ class SearchScreen(
geocoder.getFromLocationName(
searchText, 5,
lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
//lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
) {
for (address in it) {
val name: String = address.getAddressLine(0)
@@ -145,7 +143,6 @@ class SearchScreen(
isSearching = false
}
val itemList = searchItemListBuilder.build()
println("Searching ${itemList.items.size}")
invalidate()
}
}

View File

@@ -40,21 +40,21 @@ android {
}
dependencies {
implementation(libs.android.sdk.turf)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation("io.insert-koin:koin-androidx-compose:4.1.1")
implementation("io.insert-koin:koin-core:4.1.1")
implementation("io.insert-koin:koin-android:4.1.1")
implementation("io.insert-koin:koin-compose-viewmodel:4.1.1")
implementation(libs.koin.androidx.compose)
implementation(libs.koin.core)
implementation(libs.koin.android)
implementation(libs.koin.compose.viewmodel)
// objectbox
implementation("io.objectbox:objectbox-kotlin:5.0.1")
implementation(libs.objectbox.kotlin)
implementation(libs.androidx.material3)
annotationProcessor("io.objectbox:objectbox-processor:5.0.1")
annotationProcessor(libs.objectbox.processor)
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation(libs.kotlinx.serialization.json)
implementation(libs.maplibre.compose)
testImplementation(libs.junit)

View File

@@ -57,9 +57,8 @@ data class StepData (
var bearing: Double
)
//val places = mutableListOf<Place>()
/* Place(
val dataPlaces = listOf(
Place(
id = 0,
name = "Vogelhartstr. 17",
category = "Favorites",
@@ -80,7 +79,7 @@ data class StepData (
city = "München",
street = "Hohenwaldeckstr. 27",
)
) */
)
// GeoJSON data classes
@Serializable
@@ -106,7 +105,6 @@ data class Locations (
var lat : Double,
var lon : Double,
var street : String = ""
)
@Serializable
@@ -120,6 +118,8 @@ data class ValhallaLocation (
object Constants {
const val STYLE: String = "https://kouros-online.de/liberty2"
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
const val TAG: String = "Navigation"
const val CONTACTS: String = "Contacts"

View File

@@ -52,7 +52,7 @@ class NavigationRepository {
val route = getRoute(currentLocation, location)
val routeModel = RouteModel()
routeModel.startNavigation(route)
return routeModel.routeDistance
return routeModel.route.distance
}
fun getPlaces(): List<Place> {
@@ -87,7 +87,6 @@ class NavigationRepository {
)
}
})
println(url)
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
httpURLConnection.setRequestProperty(

View File

@@ -0,0 +1,115 @@
package com.kouros.navigation.data
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.valhalla.Maneuvers
import com.kouros.navigation.data.valhalla.Summary
import com.kouros.navigation.data.valhalla.Trip
import com.kouros.navigation.data.valhalla.ValhallaJson
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
import org.maplibre.geojson.Point
data class Route (
/**
* A Leg is a route between only two waypoints.
*
* @since 1.0.0
*/
val maneuvers: List<Maneuvers>,
/**
* The distance traveled from origin to destination.
*
* @return a double number with unit meters
* @since 1.0.0
*/
val distance: Double,
/**
* List of [List<Double>] objects. Each `waypoint` is an input coordinate
* snapped to the road and path network. The `waypoint` appear in the list in the order of
* the input coordinates.
*
* @since 1.0.0
*/
var waypoints: List<List<Double>>,
val pointLocations : List<Point>,
val summary: Summary,
val trip: Trip,
val time: Double,
var routingManeuvers : List<Maneuvers>,
var routeGeoJson : String,
var currentIndex: Int
) {
class Builder {
private lateinit var maneuvers: List<Maneuvers>
private var distance: Double = 0.0
private var time: Double = 0.0
private lateinit var waypoints: List<List<Double>>
private lateinit var pointLocations: List<Point>
private lateinit var summary : Summary
private lateinit var trip : Trip
private lateinit var routingManeuvers: List<Maneuvers>
private var routeGeoJson = ""
fun route (route: String ) = apply {
if (route.isNotEmpty() && route != "[]") {
val gson = GsonBuilder().serializeNulls().create()
val valhalla = gson.fromJson(route, ValhallaJson::class.java)
trip = valhalla.trip
}
}
fun build(): Route {
maneuvers = trip.legs[0].maneuvers
summary = trip.summary
distance = summary.length
time = summary.time
waypoints = decodePolyline(trip.legs[0].shape)
val points = mutableListOf<Point>()
for (loc in waypoints) {
val point = Point.fromLngLat(loc[0], loc[1])
points.add(point)
}
pointLocations = points
val routings = mutableListOf<Maneuvers>()
for (maneuver in maneuvers) {
routings.add(maneuver)
}
this.routingManeuvers = routings
this.routeGeoJson = createGeoJson(waypoints)
return Route(
maneuvers, distance, waypoints, pointLocations, summary, trip, time, routingManeuvers, routeGeoJson, 0
)
}
}
fun clear() {
waypoints = mutableListOf()
routingManeuvers = mutableListOf()
routeGeoJson = ""
}
fun currentManeuver() : Maneuvers {
return maneuvers[currentIndex]
}
fun nextManeuver() : Maneuvers {
return maneuvers[currentIndex+1]
}
}

View File

@@ -0,0 +1,17 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Legs (
@SerializedName("maneuvers" ) var maneuvers : ArrayList<Maneuvers> = arrayListOf(),
@SerializedName("summary" ) var summary : Summary = Summary(),
@SerializedName("shape" ) var shape : String = ""
)

View File

@@ -0,0 +1,19 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Locations (
@SerializedName("type" ) var type : String = "",
@SerializedName("lat" ) var lat : Double = 0.0,
@SerializedName("lon" ) var lon : Double = 0.0,
@SerializedName("side_of_street" ) var sideOfStreet : String = "",
@SerializedName("original_index" ) var originalIndex : Int = 0
)

View File

@@ -0,0 +1,29 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Maneuvers(
@SerializedName("begin_shape_index") var beginShapeIndex: Int,
@SerializedName("end_shape_index") var endShapeIndex: Int,
@SerializedName("type") var type: Int = 0,
@SerializedName("instruction") var instruction: String = "",
@SerializedName("verbal_succinct_transition_instruction") var verbalSuccinctTransitionInstruction: String = "",
@SerializedName("verbal_pre_transition_instruction") var verbalPreTransitionInstruction: String = "",
@SerializedName("verbal_post_transition_instruction") var verbalPostTransitionInstruction: String = "",
@SerializedName("street_names") val streetNames: List<String>? = arrayListOf(),
@SerializedName("bearing_after") var bearingAfter: Int = 0,
@SerializedName("time") var time: Double = 0.0,
@SerializedName("length") var length: Double = 0.0,
@SerializedName("cost") var cost: Double = 0.0,
@SerializedName("verbal_multi_cue") var verbalMultiCue: Boolean = false,
@SerializedName("travel_mode") var travelMode: String = "",
@SerializedName("travel_type") var travelType: String = "",
)

View File

@@ -0,0 +1,25 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Summary (
@SerializedName("has_time_restrictions" ) var hasTimeRestrictions : Boolean = false,
@SerializedName("has_toll" ) var hasToll : Boolean = false,
@SerializedName("has_highway" ) var hasHighway : Boolean = false,
@SerializedName("has_ferry" ) var hasFerry : Boolean = false,
@SerializedName("min_lat" ) var minLat : Double = 0.0,
@SerializedName("min_lon" ) var minLon : Double = 0.0,
@SerializedName("max_lat" ) var maxLat : Double = 0.0,
@SerializedName("max_lon" ) var maxLon : Double = 0.0,
@SerializedName("time" ) var time : Double = 0.0,
@SerializedName("length" ) var length : Double = 0.0,
@SerializedName("cost" ) var cost : Double = 0.0
)

View File

@@ -0,0 +1,21 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Trip (
@SerializedName("locations" ) var locations : ArrayList<Locations> = arrayListOf(),
@SerializedName("legs" ) var legs : ArrayList<Legs> = arrayListOf(),
@SerializedName("summary" ) var summary : Summary = Summary(),
@SerializedName("status_message" ) var statusMessage : String = "",
@SerializedName("status" ) var status : Int = 0,
@SerializedName("units" ) var units : String = "",
@SerializedName("language" ) var language : String = "",
)

View File

@@ -0,0 +1,16 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class ValhallaJson (
@SerializedName("trip" ) var trip : Trip = Trip(),
@SerializedName("id" ) var id : String = ""
)

View File

@@ -2,32 +2,29 @@ package com.kouros.navigation.model
import android.location.Location
import android.location.LocationManager
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.Utils.decodePolyline
import org.json.JSONArray
import org.json.JSONObject
import com.kouros.navigation.utils.location
import org.maplibre.geojson.Point
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
open class RouteModel() {
var polylineLocations: List<List<Double>> = emptyList()
lateinit var maneuvers: JSONArray
lateinit var locations: JSONArray
private lateinit var summary: JSONObject
var routeDistance = 0.0
var routeTime = 0.0
lateinit var centerLocation: Location
lateinit var destination: Place
var navigating = false
var arrived = false
var maneuverIndex = 0
var maneuverType = 0
/*
Index in a maneuver
*/
var currentIndex = 0
var distanceToStepEnd = 0F
@@ -38,53 +35,27 @@ open class RouteModel () {
var endIndex = 0
var routingManeuvers = mutableListOf<JSONObject>()
var 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(valhallaRoute)
val trip = jObject.getJSONObject("trip")
locations = trip.getJSONArray("locations")
val legs = trip.getJSONArray("legs")
summary = trip.getJSONObject("summary")
routeTime = summary.getDouble("time")
routeDistance = summary.getDouble("length")
centerLocation = createCenterLocation()
maneuvers = legs.getJSONObject(0).getJSONArray("maneuvers")
val shape = legs.getJSONObject(0).getString("shape")
polylineLocations = decodePolyline(shape)
}
private fun createCenterLocation() : Location {
val latitude = summary.getDouble("max_lat") - (summary.getDouble("max_lat") - summary.getDouble("min_lat"))
val longitude = summary.getDouble("max_lon") - (summary.getDouble("max_lon") - summary.getDouble("min_lon"))
return NavigationUtils().location(latitude, longitude)
}
lateinit var route: Route
fun startNavigation(valhallaRoute: String) {
decodeValhallaRoute(valhallaRoute)
for (i in 0..<maneuvers.length()) {
val maneuver = (maneuvers[i] as JSONObject)
routingManeuvers.add(maneuver)
}
route = createGeoJson(polylineLocations)
route = Route.Builder()
.route(valhallaRoute)
.build()
centerLocation = createCenterLocation()
navigating = true
}
private fun createCenterLocation(): Location {
if (route.summary.maxLat == 0.0) {
return location(homeLocation.latitude, homeLocation.longitude)
}
val latitude =
route.summary.maxLat - (route.summary.maxLat - route.summary.minLat)
val longitude =
route.summary.maxLon - (route.summary.maxLon - route.summary.minLon)
return location(latitude, longitude)
}
val currentDistance: Double
/** Returns the current [Step] with information such as the cue text and images. */
get() {
@@ -93,45 +64,42 @@ open class RouteModel () {
fun updateLocation(location: Location) {
var nearestDistance = 100000.0f
maneuverIndex = -1
route.currentIndex = -1
// find maneuver
for (i in 0..<maneuvers.length()) {
val maneuver = (maneuvers[i] as JSONObject)
val beginShapeIndex = maneuver.getString("begin_shape_index").toInt()
val endShapeIndex = maneuver.getString("end_shape_index").toInt()
for ((i, maneuver) in route.maneuvers.withIndex()) {
val beginShapeIndex = maneuver.beginShapeIndex
val endShapeIndex = maneuver.endShapeIndex
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
if (distance < nearestDistance) {
nearestDistance = distance
maneuverIndex = i
route.currentIndex = i
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
}
}
}
fun currentStep(): StepData {
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
val maneuver = route.currentManeuver()
var text = ""
if (maneuver.optJSONArray("street_names") != null) {
text = maneuver.getJSONArray("street_names").get(0) as String
println("Maneuver $maneuver")
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
if (bearing == 0F) {
if (maneuver.has("bearing_after")) {
bearing = maneuver.getInt("bearing_after").toFloat()
}
bearing = maneuver.bearingAfter.toFloat()
}
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
if (route.currentIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
}
}
}
return StepData(text, distanceStepLeft, bearing.toDouble())
}
/** Calculates the index in a maneuver. */
@@ -143,8 +111,8 @@ open class RouteModel () {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = polylineLocations[i][0]
polylineLocation.latitude = polylineLocations[i][1]
polylineLocation.longitude = route.waypoints[i][0]
polylineLocation.latitude = route.waypoints[i][1]
val distance: Float = location.distanceTo(polylineLocation)
if (distance < nearestLocation) {
nearestLocation = distance
@@ -154,16 +122,16 @@ open class RouteModel () {
distanceToStepEnd = 0F
val loc1 = Location(LocationManager.GPS_PROVIDER)
val loc2 = Location(LocationManager.GPS_PROVIDER)
loc1.longitude = polylineLocations[i][0]
loc1.latitude = polylineLocations[i][1]
loc2.longitude = polylineLocations[i+1][0]
loc2.latitude = polylineLocations[i+1][1]
loc1.longitude = route.waypoints[i][0]
loc1.latitude = route.waypoints[i][1]
loc2.longitude = route.waypoints[i + 1][0]
loc2.latitude = route.waypoints[i + 1][1]
bearing = loc1.bearingTo(loc2).absoluteValue
for (j in i + 1..endShapeIndex) {
loc1.longitude = polylineLocations[j - 1][0]
loc1.latitude = polylineLocations[j - 1][1]
loc2.longitude = polylineLocations[j][0]
loc2.latitude = polylineLocations[j][1]
loc1.longitude = route.waypoints[j - 1][0]
loc1.latitude = route.waypoints[j - 1][1]
loc2.longitude = route.waypoints[j][0]
loc2.latitude = route.waypoints[j][1]
distanceToStepEnd += loc1.distanceTo(loc2)
}
}
@@ -178,8 +146,8 @@ open class RouteModel () {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = polylineLocations[i][0]
polylineLocation.latitude = polylineLocations[i][1]
polylineLocation.longitude = route.waypoints[i][0]
polylineLocation.latitude = route.waypoints[i][1]
val distance: Float = location.distanceTo(polylineLocation)
if (distance < nearestLocation) {
nearestLocation = distance
@@ -188,15 +156,21 @@ open class RouteModel () {
return nearestLocation
}
fun maneuverLocations(): List<Point> {
val beginShapeIndex = route.currentManeuver().beginShapeIndex
val endShapeIndex = route.currentManeuver().endShapeIndex
return route.pointLocations.subList(beginShapeIndex, endShapeIndex)
}
fun travelLeftTime(): Double {
var timeLeft = 0.0
for (i in maneuverIndex + 1..<routingManeuvers.size) {
val maneuver = routingManeuvers[i]
timeLeft += maneuver.getDouble("time")
for (i in route.currentIndex + 1..<route.routingManeuvers.size) {
val maneuver = route.routingManeuvers[i]
timeLeft += maneuver.time
}
if (endIndex > 0) {
val maneuver = routingManeuvers[maneuverIndex]
val curTime = maneuver.getDouble("time")
val maneuver = route.currentManeuver()
val curTime = maneuver.time
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
val time = curTime * percent / 100
timeLeft += time
@@ -206,8 +180,8 @@ open class RouteModel () {
/** Returns the current [Step] left distance in km. */
fun leftStepDistance(): Double {
val maneuver = routingManeuvers[maneuverIndex]
var leftDistance = maneuver.getDouble("length")
val maneuver = route.routingManeuvers[route.currentIndex]
var leftDistance = maneuver.length
if (endIndex > 0) {
leftDistance = (distanceToStepEnd / 1000).toDouble()
}
@@ -216,13 +190,13 @@ open class RouteModel () {
fun travelLeftDistance(): Double {
var leftDistance = 0.0
for (i in maneuverIndex + 1..<routingManeuvers.size) {
val maneuver = routingManeuvers[i]
leftDistance += maneuver.getDouble("length")
for (i in route.currentIndex + 1..<route.routingManeuvers.size) {
val maneuver = route.routingManeuvers[i]
leftDistance += maneuver.length
}
if (endIndex > 0) {
val maneuver = routingManeuvers[maneuverIndex]
val curDistance = maneuver.getDouble("length")
val maneuver = route.routingManeuvers[route.currentIndex]
val curDistance = maneuver.length
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
val time = curDistance * percent / 100
leftDistance += time
@@ -239,11 +213,9 @@ open class RouteModel () {
}
fun stopNavigation() {
route.clear()
navigating = false
polylineLocations = mutableListOf()
routingManeuvers = mutableListOf()
route = ""
maneuverIndex = 0
//maneuverIndex = 0
currentIndex = 0
distanceToStepEnd = 0F
beginIndex = 0

View File

@@ -13,6 +13,7 @@ import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -43,7 +44,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val placeBox = boxStore.boxFor(Place::class)
pl.addAll(placeBox.all)
for (place in pl) {
val plLocation = NavigationUtils().location(place.latitude, place.longitude)
val plLocation = location(place.latitude, place.longitude)
val distance = repository.getRouteDistance(location, plLocation)
place.distance = distance.toFloat()
}
@@ -87,7 +88,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
address.address, 5) {
for (adr in it) {
if (addressLines.size > 1) {
val plLocation = NavigationUtils().location(adr.latitude, adr.longitude)
val plLocation = location(adr.latitude, adr.longitude)
val distance = repository.getRouteDistance(currentLocation, plLocation)
contactList.add(
Place(

View File

@@ -6,6 +6,13 @@ import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection
import com.kouros.navigation.data.GeoJsonLineString
import kotlinx.serialization.json.Json
import org.maplibre.geojson.Point
import org.maplibre.turf.TurfClassification
import org.maplibre.turf.TurfConversion
import org.maplibre.turf.TurfJoins
import org.maplibre.turf.TurfMeta
import org.maplibre.turf.TurfMisc
import org.maplibre.turf.TurfTransformation
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import kotlin.math.asin
@@ -14,8 +21,21 @@ import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin
class NavigationUtils() {
object Utils {
object NavigationUtils {
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
if (stepCoordinates.size > 1) {
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
val point = pointFeature.geometry() as Point
location.latitude = point.latitude()
location.longitude = point.longitude()
}
return location
}
fun decodePolyline(encoded: String, vararg precisionOptional: Int): List<List<Double>> {
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
val factor = 10.0.pow(precision)
@@ -58,7 +78,7 @@ class NavigationUtils() {
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
val featureCollection =
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
val jsonString = Json.Default.encodeToString(featureCollection)
val jsonString = Json.encodeToString(featureCollection)
return jsonString
}
@@ -128,4 +148,3 @@ class NavigationUtils() {
location.latitude = latitude
return location
}
}

View File

@@ -1,18 +1,24 @@
[versions]
agp = "8.13.1"
androidSdkTurf = "6.0.1"
gradle = "8.13.1"
koinAndroid = "4.1.1"
koinAndroidxCompose = "4.1.1"
koinComposeViewmodel = "4.1.1"
koinCore = "4.1.1"
kotlin = "2.2.21"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
kotlinxSerializationJson = "1.9.0"
lifecycleRuntimeKtx = "2.9.4"
composeBom = "2025.11.00"
appcompat = "1.7.1"
material = "1.13.0"
carApp = "1.7.0"
#objectboxKotlin = "5.0.1"
objectboxKotlin = "5.0.1"
objectboxProcessor = "5.0.1"
ui = "1.9.4"
material3 = "1.4.0"
runtimeLivedata = "1.9.4"
@@ -24,6 +30,7 @@ runtime = "1.9.4"
accompanist = "0.32.0"
[libraries]
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -33,10 +40,16 @@ androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecyc
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koinAndroid" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koinAndroidxCompose" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koinComposeViewmodel" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koinCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }
#objectbox-kotlin = { module = "io.objectbox:objectbox-kotlin", version.ref = "objectboxKotlin" }
objectbox-kotlin = { module = "io.objectbox:objectbox-kotlin", version.ref = "objectboxKotlin" }
objectbox-processor = { module = "io.objectbox:objectbox-processor", version.ref = "objectboxProcessor" }
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-composeMaterial3" }