Serialize Json
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -63,11 +63,12 @@ class SurfaceRenderer(
|
||||
|
||||
val previewRouteData = MutableLiveData("")
|
||||
|
||||
lateinit var centerLocation : Location
|
||||
lateinit var centerLocation: Location
|
||||
var preview = false
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -149,7 +148,7 @@ class NavigationScreen(
|
||||
}
|
||||
return RoutingInfo.Builder()
|
||||
.setCurrentStep(
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
routeModel.currentStep(carContext = carContext),
|
||||
Distance.create(currentDistance, displayUnit)
|
||||
)
|
||||
.setNextStep(routeModel.nextStep(carContext = carContext))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
115
common/data/src/main/java/com/kouros/navigation/data/Route.kt
Normal file
115
common/data/src/main/java/com/kouros/navigation/data/Route.kt
Normal 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]
|
||||
}
|
||||
}
|
||||
@@ -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 = ""
|
||||
|
||||
)
|
||||
@@ -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
|
||||
|
||||
)
|
||||
@@ -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 = "",
|
||||
|
||||
)
|
||||
@@ -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
|
||||
|
||||
)
|
||||
@@ -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 = "",
|
||||
|
||||
)
|
||||
@@ -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 = ""
|
||||
|
||||
)
|
||||
@@ -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
|
||||
|
||||
open class RouteModel() {
|
||||
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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,118 +21,130 @@ import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
|
||||
class NavigationUtils() {
|
||||
object Utils {
|
||||
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)
|
||||
|
||||
var lat = 0
|
||||
var lng = 0
|
||||
val coordinates = mutableListOf<List<Double>>()
|
||||
var index = 0
|
||||
|
||||
while (index < encoded.length) {
|
||||
var byte = 0x20
|
||||
var shift = 0
|
||||
var result = 0
|
||||
while (byte >= 0x20) {
|
||||
byte = encoded[index].code - 63
|
||||
result = result or ((byte and 0x1f) shl shift)
|
||||
shift += 5
|
||||
index++
|
||||
}
|
||||
lat += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
|
||||
object NavigationUtils {
|
||||
|
||||
byte = 0x20
|
||||
shift = 0
|
||||
result = 0
|
||||
while (byte >= 0x20) {
|
||||
byte = encoded[index].code - 63
|
||||
result = result or ((byte and 0x1f) shl shift)
|
||||
shift += 5
|
||||
index++
|
||||
}
|
||||
lng += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
|
||||
coordinates.add(listOf(lng.toDouble() / factor, lat.toDouble() / factor))
|
||||
}
|
||||
|
||||
return coordinates
|
||||
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()
|
||||
}
|
||||
|
||||
fun createGeoJson(lineCoordinates: List<List<Double>>): String {
|
||||
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
|
||||
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
|
||||
val featureCollection =
|
||||
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
|
||||
val jsonString = Json.Default.encodeToString(featureCollection)
|
||||
return jsonString
|
||||
}
|
||||
|
||||
fun getBoundingBox(
|
||||
lat: Double,
|
||||
lon: Double,
|
||||
radius: Double
|
||||
): Map<String, Map<String, Double>> {
|
||||
val earthRadius = 6371.0
|
||||
val maxLat = lat + Math.toDegrees(radius / earthRadius)
|
||||
val minLat = lat - Math.toDegrees(radius / earthRadius)
|
||||
val maxLon = lon + Math.toDegrees(radius / earthRadius / cos(Math.toRadians(lat)))
|
||||
val minLon = lon - Math.toDegrees(radius / earthRadius / cos(Math.toRadians(lat)))
|
||||
|
||||
return mapOf(
|
||||
"nw" to mapOf("lat" to maxLat, "lon" to minLon),
|
||||
"ne" to mapOf("lat" to maxLat, "lon" to maxLon),
|
||||
"sw" to mapOf("lat" to minLat, "lon" to minLon),
|
||||
"se" to mapOf("lat" to minLat, "lon" to maxLon)
|
||||
)
|
||||
}
|
||||
|
||||
fun computeOffset(from: Location, distance: Double, heading: Double): Location {
|
||||
val earthRadius = 6371009.0
|
||||
var distance = distance
|
||||
var heading = heading
|
||||
distance /= earthRadius
|
||||
heading = toRadians(heading)
|
||||
val fromLat: Double = toRadians(from.latitude)
|
||||
val fromLng: Double = toRadians(from.longitude)
|
||||
val cosDistance: Double = cos(distance)
|
||||
val sinDistance = sin(distance)
|
||||
val sinFromLat = sin(fromLat)
|
||||
val cosFromLat: Double = cos(fromLat)
|
||||
val sinLat: Double = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading)
|
||||
val dLng: Double = atan2(
|
||||
sinDistance * cosFromLat * sin(heading),
|
||||
cosDistance - sinFromLat * sinLat
|
||||
)
|
||||
val snap = Location(LocationManager.GPS_PROVIDER)
|
||||
snap.latitude = toDegrees(asin(sinLat))
|
||||
snap.longitude = toDegrees(fromLng + dLng)
|
||||
return snap
|
||||
//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()
|
||||
}
|
||||
|
||||
fun location(latitude: Double, longitude: Double): Location {
|
||||
val location = Location(LocationManager.GPS_PROVIDER)
|
||||
location.longitude = longitude
|
||||
location.latitude = latitude
|
||||
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)
|
||||
|
||||
var lat = 0
|
||||
var lng = 0
|
||||
val coordinates = mutableListOf<List<Double>>()
|
||||
var index = 0
|
||||
|
||||
while (index < encoded.length) {
|
||||
var byte = 0x20
|
||||
var shift = 0
|
||||
var result = 0
|
||||
while (byte >= 0x20) {
|
||||
byte = encoded[index].code - 63
|
||||
result = result or ((byte and 0x1f) shl shift)
|
||||
shift += 5
|
||||
index++
|
||||
}
|
||||
lat += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
|
||||
|
||||
byte = 0x20
|
||||
shift = 0
|
||||
result = 0
|
||||
while (byte >= 0x20) {
|
||||
byte = encoded[index].code - 63
|
||||
result = result or ((byte and 0x1f) shl shift)
|
||||
shift += 5
|
||||
index++
|
||||
}
|
||||
lng += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
|
||||
coordinates.add(listOf(lng.toDouble() / factor, lat.toDouble() / factor))
|
||||
}
|
||||
|
||||
return coordinates
|
||||
}
|
||||
|
||||
fun createGeoJson(lineCoordinates: List<List<Double>>): String {
|
||||
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
|
||||
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
|
||||
val featureCollection =
|
||||
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
|
||||
val jsonString = Json.encodeToString(featureCollection)
|
||||
return jsonString
|
||||
}
|
||||
|
||||
fun getBoundingBox(
|
||||
lat: Double,
|
||||
lon: Double,
|
||||
radius: Double
|
||||
): Map<String, Map<String, Double>> {
|
||||
val earthRadius = 6371.0
|
||||
val maxLat = lat + Math.toDegrees(radius / earthRadius)
|
||||
val minLat = lat - Math.toDegrees(radius / earthRadius)
|
||||
val maxLon = lon + Math.toDegrees(radius / earthRadius / cos(Math.toRadians(lat)))
|
||||
val minLon = lon - Math.toDegrees(radius / earthRadius / cos(Math.toRadians(lat)))
|
||||
|
||||
return mapOf(
|
||||
"nw" to mapOf("lat" to maxLat, "lon" to minLon),
|
||||
"ne" to mapOf("lat" to maxLat, "lon" to maxLon),
|
||||
"sw" to mapOf("lat" to minLat, "lon" to minLon),
|
||||
"se" to mapOf("lat" to minLat, "lon" to maxLon)
|
||||
)
|
||||
}
|
||||
|
||||
fun computeOffset(from: Location, distance: Double, heading: Double): Location {
|
||||
val earthRadius = 6371009.0
|
||||
var distance = distance
|
||||
var heading = heading
|
||||
distance /= earthRadius
|
||||
heading = toRadians(heading)
|
||||
val fromLat: Double = toRadians(from.latitude)
|
||||
val fromLng: Double = toRadians(from.longitude)
|
||||
val cosDistance: Double = cos(distance)
|
||||
val sinDistance = sin(distance)
|
||||
val sinFromLat = sin(fromLat)
|
||||
val cosFromLat: Double = cos(fromLat)
|
||||
val sinLat: Double = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading)
|
||||
val dLng: Double = atan2(
|
||||
sinDistance * cosFromLat * sin(heading),
|
||||
cosDistance - sinFromLat * sinLat
|
||||
)
|
||||
val snap = Location(LocationManager.GPS_PROVIDER)
|
||||
snap.latitude = toDegrees(asin(sinLat))
|
||||
snap.longitude = toDegrees(fromLng + dLng)
|
||||
return snap
|
||||
//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()
|
||||
}
|
||||
|
||||
fun location(latitude: Double, longitude: Double): Location {
|
||||
val location = Location(LocationManager.GPS_PROVIDER)
|
||||
location.longitude = longitude
|
||||
location.latitude = latitude
|
||||
return location
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user