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
|
package com.kouros.navigation
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
@@ -21,7 +5,6 @@ import android.annotation.SuppressLint
|
|||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
@@ -47,7 +30,7 @@ import androidx.compose.material3.SnackbarHost
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -68,14 +51,16 @@ import androidx.lifecycle.Observer
|
|||||||
import com.example.places.ui.theme.PlacesTheme
|
import com.example.places.ui.theme.PlacesTheme
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
|
import com.kouros.android.cars.carappservice.R
|
||||||
|
|
||||||
import com.kouros.navigation.data.Category
|
import com.kouros.navigation.data.Category
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.model.ViewModel
|
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 kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
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.expressions.dsl.const
|
||||||
import org.maplibre.compose.layers.FillLayer
|
import org.maplibre.compose.layers.FillLayer
|
||||||
import org.maplibre.compose.layers.LineLayer
|
import org.maplibre.compose.layers.LineLayer
|
||||||
|
import org.maplibre.compose.location.DesiredAccuracy
|
||||||
import org.maplibre.compose.location.LocationPuck
|
import org.maplibre.compose.location.LocationPuck
|
||||||
import org.maplibre.compose.location.LocationPuckColors
|
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.rememberDefaultLocationProvider
|
||||||
import org.maplibre.compose.location.rememberUserLocationState
|
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.MaplibreMap
|
||||||
import org.maplibre.compose.map.OrnamentOptions
|
|
||||||
import org.maplibre.compose.sources.GeoJsonData
|
import org.maplibre.compose.sources.GeoJsonData
|
||||||
import org.maplibre.compose.sources.getBaseSource
|
import org.maplibre.compose.sources.getBaseSource
|
||||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||||
@@ -99,24 +84,24 @@ import org.maplibre.spatialk.geojson.Position
|
|||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
||||||
val routeData = MutableLiveData("")
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
val routeData = MutableLiveData("")
|
||||||
|
|
||||||
val vieModel = ViewModel(NavigationRepository())
|
val vieModel = ViewModel(NavigationRepository())
|
||||||
val routeModel = RouteModel()
|
val routeModel = RouteModel()
|
||||||
|
|
||||||
var tilt = 0.0
|
var tilt = 50.0
|
||||||
|
|
||||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
|
||||||
|
|
||||||
val instruction: MutableLiveData<StepData> by lazy {
|
val instruction: MutableLiveData<StepData> by lazy {
|
||||||
MutableLiveData<StepData>()
|
MutableLiveData<StepData>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lastLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
|
|
||||||
val observer = Observer<String> { newRoute ->
|
val observer = Observer<String> { newRoute ->
|
||||||
routeModel.startNavigation(newRoute)
|
routeModel.startNavigation(newRoute)
|
||||||
routeData.value = routeModel.route
|
routeData.value = routeModel.route.routeGeoJson
|
||||||
}
|
}
|
||||||
|
|
||||||
val cameraPosition = MutableLiveData(
|
val cameraPosition = MutableLiveData(
|
||||||
@@ -126,40 +111,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var locationIndex = 0
|
||||||
|
|
||||||
|
var test = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
vieModel.route.observe(this, observer)
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -198,10 +157,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar("Starte Navigation")
|
snackbarHostState.showSnackbar("Starte Navigation")
|
||||||
}
|
}
|
||||||
if (!routeModel.isNavigating()) {
|
if (!routeModel.isNavigating() && lastLocation.latitude != 0.0) {
|
||||||
tilt = 60.0
|
tilt = 60.0
|
||||||
vieModel.loadRoute(
|
vieModel.loadRoute(
|
||||||
curLocation,
|
lastLocation,
|
||||||
Constants.home2Location
|
Constants.home2Location
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -271,8 +230,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
Card {
|
Card {
|
||||||
Column {
|
Column {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(com.kouros.android.cars.carappservice.R.drawable.ic_turn_normal_right),
|
painter = painterResource(R.drawable.ic_turn_normal_right),
|
||||||
contentDescription = stringResource(id = com.kouros.android.cars.carappservice.R.string.accept_action_title)
|
contentDescription = stringResource(id = R.string.accept_action_title)
|
||||||
)
|
)
|
||||||
if (step != null) {
|
if (step != null) {
|
||||||
Text(text = step.bearing.toString(), fontSize = 25.sp)
|
Text(text = step.bearing.toString(), fontSize = 25.sp)
|
||||||
@@ -295,10 +254,23 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapView() {
|
fun MapView() {
|
||||||
val locationProvider = rememberDefaultLocationProvider()
|
val locationProvider = rememberDefaultLocationProvider(
|
||||||
val locationState = rememberUserLocationState(locationProvider)
|
updateInterval = 0.1.seconds,
|
||||||
updateLocation(locationState.location)
|
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 position: CameraPosition? by cameraPosition.observeAsState()
|
||||||
|
|
||||||
val route: String? by routeData.observeAsState()
|
val route: String? by routeData.observeAsState()
|
||||||
val cameraState =
|
val cameraState =
|
||||||
rememberCameraState(
|
rememberCameraState(
|
||||||
@@ -311,42 +283,32 @@ class MainActivity : ComponentActivity() {
|
|||||||
zoom = 15.0,
|
zoom = 15.0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (locationState.location != null) {
|
|
||||||
curLocation.latitude = locationState.location?.position!!.latitude
|
|
||||||
curLocation.longitude = locationState.location?.position!!.longitude
|
|
||||||
}
|
|
||||||
MaplibreMap(
|
MaplibreMap(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
|
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
||||||
baseStyle = BaseStyle.Uri("https://kouros-online.de/liberty"),
|
|
||||||
options =
|
|
||||||
MapOptions(
|
|
||||||
gestureOptions = GestureOptions(
|
|
||||||
isTiltEnabled = true,
|
|
||||||
isZoomEnabled = true,
|
|
||||||
isRotateEnabled = false,
|
|
||||||
isScrollEnabled = true,
|
|
||||||
),
|
|
||||||
ornamentOptions = OrnamentOptions(
|
|
||||||
isScaleBarEnabled = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
LocationPuck(
|
|
||||||
idPrefix = "user-location",
|
|
||||||
locationState = locationState,
|
|
||||||
cameraState = cameraState,
|
|
||||||
accuracyThreshold = 10f,
|
|
||||||
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
|
|
||||||
)
|
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
||||||
RouteLayer(route)
|
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(
|
cameraState.animateTo(
|
||||||
finalPosition = CameraPosition(
|
finalPosition = CameraPosition(
|
||||||
bearing = position!!.bearing,
|
bearing = position!!.bearing,
|
||||||
@@ -354,9 +316,22 @@ class MainActivity : ComponentActivity() {
|
|||||||
target = position!!.target,
|
target = position!!.target,
|
||||||
tilt = tilt
|
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
|
@Composable
|
||||||
@@ -368,17 +343,66 @@ class MainActivity : ComponentActivity() {
|
|||||||
id = "routes-casing",
|
id = "routes-casing",
|
||||||
source = routes,
|
source = routes,
|
||||||
color = const(Color.White),
|
color = const(Color.White),
|
||||||
width = const(6.dp),
|
width = const(10.dp),
|
||||||
)
|
)
|
||||||
LineLayer(
|
LineLayer(
|
||||||
id = "routes",
|
id = "routes",
|
||||||
source = routes,
|
source = routes,
|
||||||
color = const(Color.Blue),
|
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
|
@Composable
|
||||||
fun PlaceList(viewModel: ViewModel = koinViewModel()) {
|
fun PlaceList(viewModel: ViewModel = koinViewModel()) {
|
||||||
var categories: List<Category>
|
var categories: List<Category>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.maplibre.compose)
|
implementation(libs.maplibre.compose)
|
||||||
//implementation(libs.maplibre.composeMaterial3)
|
//implementation(libs.maplibre.composeMaterial3)
|
||||||
|
|
||||||
implementation(project(":common:data"))
|
implementation(project(":common:data"))
|
||||||
implementation(libs.androidx.runtime.livedata)
|
implementation(libs.androidx.runtime.livedata)
|
||||||
implementation(libs.androidx.compose.foundation)
|
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?) {
|
fun test(location: Location?) {
|
||||||
if (routeModel.isNavigating() && locationIndex < routeModel.polylineLocations.size) {
|
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
|
||||||
val loc = routeModel.polylineLocations[locationIndex]
|
val loc = routeModel.route.waypoints[locationIndex]
|
||||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
curLocation.longitude = loc[0]
|
curLocation.longitude = loc[0] + 0.0003
|
||||||
curLocation.latitude = loc[1]
|
curLocation.latitude = loc[1] + 0.0002
|
||||||
update(curLocation)
|
update(curLocation)
|
||||||
locationIndex += 1
|
locationIndex += 1
|
||||||
if (locationIndex > routeModel.polylineLocations.size) {
|
if (locationIndex > routeModel.route.waypoints.size) {
|
||||||
val locationManager =
|
val locationManager =
|
||||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
locationManager.removeUpdates(mLocationListener)
|
locationManager.removeUpdates(mLocationListener)
|
||||||
@@ -185,16 +185,10 @@ class NavigationSession : Session() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun update(location: Location) {
|
fun update(location: Location) {
|
||||||
surfaceRenderer.updateLocation(location)
|
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
routeModel.updateLocation(location)
|
routeModel.updateLocation(location)
|
||||||
// if (routeModel.distanceToRoute > 50) {
|
|
||||||
// routeModel.stopNavigation()
|
|
||||||
// locationIndex = 0
|
|
||||||
// surfaceRenderer.setRouteData()
|
|
||||||
// navigationScreen.reRoute()
|
|
||||||
// }
|
|
||||||
navigationScreen.updateTrip()
|
navigationScreen.updateTrip()
|
||||||
}
|
}
|
||||||
|
surfaceRenderer.updateLocation(location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,17 +26,17 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.model.RouteModel
|
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.CameraPosition
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
import org.maplibre.compose.expressions.dsl.const
|
import org.maplibre.compose.expressions.dsl.const
|
||||||
import org.maplibre.compose.layers.FillLayer
|
import org.maplibre.compose.layers.FillLayer
|
||||||
import org.maplibre.compose.layers.LineLayer
|
import org.maplibre.compose.layers.LineLayer
|
||||||
import org.maplibre.compose.location.LocationPuck
|
|
||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
import org.maplibre.compose.location.LocationPuckSizes
|
||||||
import org.maplibre.compose.location.rememberUserLocationState
|
|
||||||
import org.maplibre.compose.map.MaplibreMap
|
import org.maplibre.compose.map.MaplibreMap
|
||||||
import org.maplibre.compose.sources.GeoJsonData
|
import org.maplibre.compose.sources.GeoJsonData
|
||||||
import org.maplibre.compose.sources.getBaseSource
|
import org.maplibre.compose.sources.getBaseSource
|
||||||
@@ -63,11 +63,12 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
val previewRouteData = MutableLiveData("")
|
val previewRouteData = MutableLiveData("")
|
||||||
|
|
||||||
lateinit var centerLocation : Location
|
lateinit var centerLocation: Location
|
||||||
var preview = false
|
var preview = false
|
||||||
|
|
||||||
lateinit var mapView: ComposeView
|
lateinit var mapView: ComposeView
|
||||||
|
|
||||||
|
var panView = false
|
||||||
val tilt = 55.0
|
val tilt = 55.0
|
||||||
val padding = PaddingValues(start = 150.dp, top = 250.dp)
|
val padding = PaddingValues(start = 150.dp, top = 250.dp)
|
||||||
|
|
||||||
@@ -154,8 +155,6 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapView() {
|
fun MapView() {
|
||||||
val locationProvider = rememberDefaultLocationProvider()
|
|
||||||
val locationState = rememberUserLocationState(locationProvider)
|
|
||||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||||
val route: String? by routeData.observeAsState()
|
val route: String? by routeData.observeAsState()
|
||||||
val previewRoute: String? by previewRouteData.observeAsState()
|
val previewRoute: String? by previewRouteData.observeAsState()
|
||||||
@@ -174,18 +173,19 @@ class SurfaceRenderer(
|
|||||||
)
|
)
|
||||||
MaplibreMap(
|
MaplibreMap(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
//baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty"),
|
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
||||||
baseStyle = BaseStyle.Uri("https://kouros-online.de/liberty"),
|
|
||||||
) {
|
) {
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
FillLayer(id = "example", visible = false, source = tiles, sourceLayer = "building")
|
||||||
RouteLayer(route, previewRoute)
|
RouteLayer(route, previewRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationPuck(
|
LocationPuck(
|
||||||
idPrefix = "user-location",
|
idPrefix = "user-location",
|
||||||
locationState = locationState,
|
locationState = lastLocation,
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
accuracyThreshold = 10f,
|
accuracyThreshold = 10f,
|
||||||
|
sizes = LocationPuckSizes(dotRadius = 10.dp),
|
||||||
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
|
colors = LocationPuckColors(accuracyStrokeColor = Color.Green)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -264,6 +264,7 @@ class SurfaceRenderer(
|
|||||||
/** Handles the map zoom-in and zoom-out events. */
|
/** Handles the map zoom-in and zoom-out events. */
|
||||||
fun handleScale(zoomSign: Int) {
|
fun handleScale(zoomSign: Int) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
panView = true
|
||||||
val newZoom = if (zoomSign < 0) {
|
val newZoom = if (zoomSign < 0) {
|
||||||
cameraPosition.value!!.zoom - 1.0
|
cameraPosition.value!!.zoom - 1.0
|
||||||
} else {
|
} else {
|
||||||
@@ -281,27 +282,33 @@ class SurfaceRenderer(
|
|||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
|
var snapedLocation = location
|
||||||
var bearing: Double
|
var bearing: Double
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
|
snapedLocation = snapLocation(location, routeModel.maneuverLocations())
|
||||||
bearing = routeModel.currentStep().bearing
|
bearing = routeModel.currentStep().bearing
|
||||||
} else {
|
} else {
|
||||||
bearing = cameraPosition.value!!.bearing
|
bearing = cameraPosition.value!!.bearing
|
||||||
if (lastLocation.latitude != location.latitude) {
|
if (lastLocation.latitude != snapedLocation.latitude) {
|
||||||
if (lastLocation.distanceTo(location) > 5) {
|
if (lastLocation.distanceTo(snapedLocation) > 5) {
|
||||||
bearing = lastLocation.bearingTo(location).toDouble()
|
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.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
bearing = bearing,
|
bearing = bearing,
|
||||||
zoom = zoom,
|
zoom = zoom,
|
||||||
padding = getPaddingValues(),
|
padding = getPaddingValues(),
|
||||||
target = Position(location.longitude, location.latitude),
|
target = Position(snapedLocation.longitude, snapedLocation.latitude),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
lastLocation = location
|
lastLocation = snapedLocation
|
||||||
} else {
|
} else {
|
||||||
val bearing = 0.0
|
val bearing = 0.0
|
||||||
val zoom = 11.0
|
val zoom = 11.0
|
||||||
@@ -319,12 +326,13 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
|
|
||||||
fun setRouteData() {
|
fun setRouteData() {
|
||||||
routeData.value = routeModel.route
|
routeData.value = routeModel.route.routeGeoJson
|
||||||
preview = false
|
preview = false
|
||||||
|
panView = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPreviewRouteData(routeModel: RouteModel) {
|
fun setPreviewRouteData(routeModel: RouteModel) {
|
||||||
previewRouteData.value = routeModel.route
|
previewRouteData.value = routeModel.route.routeGeoJson
|
||||||
centerLocation = routeModel.centerLocation
|
centerLocation = routeModel.centerLocation
|
||||||
preview = true
|
preview = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ class RouteCarModel() : RouteModel() {
|
|||||||
|
|
||||||
/** Returns the current [Step] with information such as the cue text and images. */
|
/** Returns the current [Step] with information such as the cue text and images. */
|
||||||
fun currentStep(carContext: CarContext): Step {
|
fun currentStep(carContext: CarContext): Step {
|
||||||
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
|
val maneuver = route.currentManeuver()
|
||||||
val maneuverType = maneuver.getInt("type")
|
val maneuverType = maneuver.type
|
||||||
|
|
||||||
val stepData = currentStep()
|
val stepData = currentStep()
|
||||||
|
|
||||||
@@ -53,9 +53,9 @@ class RouteCarModel() : RouteModel() {
|
|||||||
}
|
}
|
||||||
when (stepData.leftDistance) {
|
when (stepData.leftDistance) {
|
||||||
in 0.0..100.0 -> {
|
in 0.0..100.0 -> {
|
||||||
if (maneuverIndex < maneuvers.length()) {
|
if (route.currentIndex < route.maneuvers.size) {
|
||||||
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
|
val maneuver = route.nextManeuver()
|
||||||
val maneuverType = maneuver.getInt("type")
|
val maneuverType = maneuver.type
|
||||||
routing = routingData(maneuverType, carContext)
|
routing = routingData(maneuverType, carContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,22 +77,22 @@ class RouteCarModel() : RouteModel() {
|
|||||||
|
|
||||||
/** Returns the next [Step] with information such as the cue text and images. */
|
/** Returns the next [Step] with information such as the cue text and images. */
|
||||||
fun nextStep(carContext: CarContext): Step {
|
fun nextStep(carContext: CarContext): Step {
|
||||||
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
|
val maneuver = route.nextManeuver()
|
||||||
val maneuverType = maneuver.getInt("type")
|
val maneuverType = maneuver.type
|
||||||
val routing = routingData(maneuverType, carContext)
|
val routing = routingData(maneuverType, carContext)
|
||||||
var text = ""
|
var text = ""
|
||||||
val distanceLeft = leftStepDistance() * 1000
|
val distanceLeft = leftStepDistance() * 1000
|
||||||
|
|
||||||
when (distanceLeft) {
|
when (distanceLeft) {
|
||||||
in 0.0..100.0 -> {
|
in 0.0..100.0 -> {
|
||||||
if (maneuver.optJSONArray("street_names") != null) {
|
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
||||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
text = maneuver.streetNames!![0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (maneuver.optJSONArray("street_names") != null) {
|
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
||||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
text = maneuver.streetNames!![0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,40 +113,42 @@ class RouteCarModel() : RouteModel() {
|
|||||||
var type = Maneuver.TYPE_DEPART
|
var type = Maneuver.TYPE_DEPART
|
||||||
var currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
|
var currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
|
||||||
when (routeManeuverType) {
|
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 -> {
|
ManeuverType.None.value -> {
|
||||||
type = Maneuver.TYPE_STRAIGHT
|
type = Maneuver.TYPE_STRAIGHT
|
||||||
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
|
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 -> {
|
ManeuverType.Right.value -> {
|
||||||
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
|
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
|
||||||
// currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_right)
|
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_right)
|
||||||
currentTurnIcon = createCarIcon(carContext, R.drawable.turn_right_48px1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.Left.value -> {
|
ManeuverType.Left.value -> {
|
||||||
type = Maneuver.TYPE_TURN_NORMAL_LEFT
|
type = Maneuver.TYPE_TURN_NORMAL_LEFT
|
||||||
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_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 -> {
|
ManeuverType.RampLeft.value -> {
|
||||||
type = Maneuver.TYPE_TURN_NORMAL_LEFT
|
type = Maneuver.TYPE_TURN_NORMAL_LEFT
|
||||||
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_left)
|
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_normal_left)
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.ExitRight.value -> {
|
ManeuverType.ExitRight.value -> {
|
||||||
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
|
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
|
||||||
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_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 -> {
|
ManeuverType.StayLeft.value -> {
|
||||||
type = Maneuver.TYPE_KEEP_LEFT
|
type = Maneuver.TYPE_KEEP_LEFT
|
||||||
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
|
currentTurnIcon = createCarIcon(carContext, R.drawable.ic_turn_name_change)
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
// Log.i(TAG, "onGetTemplate NavigationScreen")
|
|
||||||
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
Action.Builder()
|
Action.Builder()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.location.Location
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
|
import android.util.Log
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.CarToast
|
import androidx.car.app.CarToast
|
||||||
import androidx.car.app.Screen
|
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.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import com.kouros.navigation.data.Place
|
|||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
|
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
|
||||||
class RoutePreviewScreen(
|
class RoutePreviewScreen(
|
||||||
@@ -110,7 +109,7 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
val itemListBuilder = ItemList.Builder()
|
val itemListBuilder = ItemList.Builder()
|
||||||
|
|
||||||
if (routeModel.polylineLocations.isNotEmpty()) {
|
if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) {
|
||||||
itemListBuilder.addItem(createRow(0, navigateAction))
|
itemListBuilder.addItem(createRow(0, navigateAction))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,9 +200,8 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
|
|
||||||
private fun createRouteText(index: Int): CarText {
|
private fun createRouteText(index: Int): CarText {
|
||||||
val time = routeModel.routeTime
|
val time = routeModel.route.summary.time
|
||||||
|
val length = BigDecimal(routeModel.route.distance).setScale(1, RoundingMode.HALF_EVEN)
|
||||||
val length = BigDecimal(routeModel.routeDistance).setScale(1, RoundingMode.HALF_EVEN)
|
|
||||||
val firstRoute = SpannableString(" \u00b7 $length km")
|
val firstRoute = SpannableString(" \u00b7 $length km")
|
||||||
firstRoute.setSpan(
|
firstRoute.setSpan(
|
||||||
DurationSpan.create(time.toLong()), 0, 1,0
|
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.Category
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.utils.NavigationUtils.Utils.getBoundingBox
|
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox
|
||||||
|
|
||||||
|
|
||||||
class SearchScreen(
|
class SearchScreen(
|
||||||
@@ -42,8 +42,6 @@ class SearchScreen(
|
|||||||
|
|
||||||
val searchItemListBuilder = ItemList.Builder()
|
val searchItemListBuilder = ItemList.Builder()
|
||||||
.setNoItemsMessage("No search results to show")
|
.setNoItemsMessage("No search results to show")
|
||||||
|
|
||||||
println("OnGetTemplate SearchScreen ${categories.size}")
|
|
||||||
if (!isSearching) {
|
if (!isSearching) {
|
||||||
categories.forEach {
|
categories.forEach {
|
||||||
it.name
|
it.name
|
||||||
@@ -111,7 +109,7 @@ class SearchScreen(
|
|||||||
|
|
||||||
geocoder.getFromLocationName(
|
geocoder.getFromLocationName(
|
||||||
searchText, 5,
|
searchText, 5,
|
||||||
lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
|
//lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
|
||||||
) {
|
) {
|
||||||
for (address in it) {
|
for (address in it) {
|
||||||
val name: String = address.getAddressLine(0)
|
val name: String = address.getAddressLine(0)
|
||||||
@@ -145,7 +143,6 @@ class SearchScreen(
|
|||||||
isSearching = false
|
isSearching = false
|
||||||
}
|
}
|
||||||
val itemList = searchItemListBuilder.build()
|
val itemList = searchItemListBuilder.build()
|
||||||
println("Searching ${itemList.items.size}")
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,21 +40,21 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.android.sdk.turf)
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation("io.insert-koin:koin-androidx-compose:4.1.1")
|
implementation(libs.koin.androidx.compose)
|
||||||
implementation("io.insert-koin:koin-core:4.1.1")
|
implementation(libs.koin.core)
|
||||||
implementation("io.insert-koin:koin-android:4.1.1")
|
implementation(libs.koin.android)
|
||||||
implementation("io.insert-koin:koin-compose-viewmodel:4.1.1")
|
implementation(libs.koin.compose.viewmodel)
|
||||||
|
|
||||||
// objectbox
|
// objectbox
|
||||||
implementation("io.objectbox:objectbox-kotlin:5.0.1")
|
implementation(libs.objectbox.kotlin)
|
||||||
implementation(libs.androidx.material3)
|
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)
|
implementation(libs.maplibre.compose)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
|||||||
@@ -57,9 +57,8 @@ data class StepData (
|
|||||||
var bearing: Double
|
var bearing: Double
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val dataPlaces = listOf(
|
||||||
//val places = mutableListOf<Place>()
|
Place(
|
||||||
/* Place(
|
|
||||||
id = 0,
|
id = 0,
|
||||||
name = "Vogelhartstr. 17",
|
name = "Vogelhartstr. 17",
|
||||||
category = "Favorites",
|
category = "Favorites",
|
||||||
@@ -80,7 +79,7 @@ data class StepData (
|
|||||||
city = "München",
|
city = "München",
|
||||||
street = "Hohenwaldeckstr. 27",
|
street = "Hohenwaldeckstr. 27",
|
||||||
)
|
)
|
||||||
) */
|
)
|
||||||
|
|
||||||
// GeoJSON data classes
|
// GeoJSON data classes
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -106,7 +105,6 @@ data class Locations (
|
|||||||
var lat : Double,
|
var lat : Double,
|
||||||
var lon : Double,
|
var lon : Double,
|
||||||
var street : String = ""
|
var street : String = ""
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -120,6 +118,8 @@ data class ValhallaLocation (
|
|||||||
|
|
||||||
object Constants {
|
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 TAG: String = "Navigation"
|
||||||
|
|
||||||
const val CONTACTS: String = "Contacts"
|
const val CONTACTS: String = "Contacts"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class NavigationRepository {
|
|||||||
val route = getRoute(currentLocation, location)
|
val route = getRoute(currentLocation, location)
|
||||||
val routeModel = RouteModel()
|
val routeModel = RouteModel()
|
||||||
routeModel.startNavigation(route)
|
routeModel.startNavigation(route)
|
||||||
return routeModel.routeDistance
|
return routeModel.route.distance
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaces(): List<Place> {
|
fun getPlaces(): List<Place> {
|
||||||
@@ -87,7 +87,6 @@ class NavigationRepository {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
println(url)
|
println(url)
|
||||||
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
||||||
httpURLConnection.setRequestProperty(
|
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.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
|
import com.kouros.navigation.data.Constants.homeLocation
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.Route
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.utils.NavigationUtils
|
import com.kouros.navigation.utils.location
|
||||||
import com.kouros.navigation.utils.NavigationUtils.Utils.createGeoJson
|
import org.maplibre.geojson.Point
|
||||||
import com.kouros.navigation.utils.NavigationUtils.Utils.decodePolyline
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
open class RouteModel () {
|
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 centerLocation: Location
|
||||||
|
|
||||||
lateinit var destination: Place
|
lateinit var destination: Place
|
||||||
|
|
||||||
var navigating = false
|
var navigating = false
|
||||||
|
|
||||||
var arrived = false
|
var arrived = false
|
||||||
var maneuverIndex = 0
|
|
||||||
var maneuverType = 0
|
var maneuverType = 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
Index in a maneuver
|
||||||
|
*/
|
||||||
var currentIndex = 0
|
var currentIndex = 0
|
||||||
|
|
||||||
var distanceToStepEnd = 0F
|
var distanceToStepEnd = 0F
|
||||||
@@ -38,53 +35,27 @@ open class RouteModel () {
|
|||||||
|
|
||||||
var endIndex = 0
|
var endIndex = 0
|
||||||
|
|
||||||
var routingManeuvers = mutableListOf<JSONObject>()
|
lateinit var route: Route
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startNavigation(valhallaRoute: String) {
|
fun startNavigation(valhallaRoute: String) {
|
||||||
decodeValhallaRoute(valhallaRoute)
|
route = Route.Builder()
|
||||||
for (i in 0..<maneuvers.length()) {
|
.route(valhallaRoute)
|
||||||
val maneuver = (maneuvers[i] as JSONObject)
|
.build()
|
||||||
routingManeuvers.add(maneuver)
|
centerLocation = createCenterLocation()
|
||||||
}
|
|
||||||
route = createGeoJson(polylineLocations)
|
|
||||||
navigating = true
|
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
|
val currentDistance: Double
|
||||||
/** Returns the current [Step] with information such as the cue text and images. */
|
/** Returns the current [Step] with information such as the cue text and images. */
|
||||||
get() {
|
get() {
|
||||||
@@ -93,45 +64,42 @@ open class RouteModel () {
|
|||||||
|
|
||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
var nearestDistance = 100000.0f
|
var nearestDistance = 100000.0f
|
||||||
maneuverIndex = -1
|
route.currentIndex = -1
|
||||||
// find maneuver
|
// find maneuver
|
||||||
for (i in 0..<maneuvers.length()) {
|
for ((i, maneuver) in route.maneuvers.withIndex()) {
|
||||||
val maneuver = (maneuvers[i] as JSONObject)
|
val beginShapeIndex = maneuver.beginShapeIndex
|
||||||
val beginShapeIndex = maneuver.getString("begin_shape_index").toInt()
|
val endShapeIndex = maneuver.endShapeIndex
|
||||||
val endShapeIndex = maneuver.getString("end_shape_index").toInt()
|
|
||||||
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
|
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
|
||||||
if (distance < nearestDistance) {
|
if (distance < nearestDistance) {
|
||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
maneuverIndex = i
|
route.currentIndex = i
|
||||||
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
|
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentStep(): StepData {
|
fun currentStep(): StepData {
|
||||||
val maneuver = (maneuvers[maneuverIndex] as JSONObject)
|
val maneuver = route.currentManeuver()
|
||||||
var text = ""
|
var text = ""
|
||||||
if (maneuver.optJSONArray("street_names") != null) {
|
println("Maneuver $maneuver")
|
||||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||||
|
text = maneuver.streetNames[0]
|
||||||
}
|
}
|
||||||
if (bearing == 0F) {
|
if (bearing == 0F) {
|
||||||
if (maneuver.has("bearing_after")) {
|
bearing = maneuver.bearingAfter.toFloat()
|
||||||
bearing = maneuver.getInt("bearing_after").toFloat()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val distanceStepLeft = leftStepDistance() * 1000
|
val distanceStepLeft = leftStepDistance() * 1000
|
||||||
when (distanceStepLeft) {
|
when (distanceStepLeft) {
|
||||||
in 0.0..100.0 -> {
|
in 0.0..100.0 -> {
|
||||||
if (maneuverIndex < maneuvers.length()) {
|
if (route.currentIndex < route.maneuvers.size) {
|
||||||
val maneuver = (maneuvers[maneuverIndex + 1] as JSONObject)
|
val maneuver = route.nextManeuver()
|
||||||
if (maneuver.optJSONArray("street_names") != null) {
|
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||||
text = maneuver.getJSONArray("street_names").get(0) as String
|
text = maneuver.streetNames[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return StepData(text, distanceStepLeft, bearing.toDouble())
|
return StepData(text, distanceStepLeft, bearing.toDouble())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calculates the index in a maneuver. */
|
/** Calculates the index in a maneuver. */
|
||||||
@@ -143,8 +111,8 @@ open class RouteModel () {
|
|||||||
var nearestLocation = 100000.0f
|
var nearestLocation = 100000.0f
|
||||||
for (i in beginShapeIndex..endShapeIndex) {
|
for (i in beginShapeIndex..endShapeIndex) {
|
||||||
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
|
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
polylineLocation.longitude = polylineLocations[i][0]
|
polylineLocation.longitude = route.waypoints[i][0]
|
||||||
polylineLocation.latitude = polylineLocations[i][1]
|
polylineLocation.latitude = route.waypoints[i][1]
|
||||||
val distance: Float = location.distanceTo(polylineLocation)
|
val distance: Float = location.distanceTo(polylineLocation)
|
||||||
if (distance < nearestLocation) {
|
if (distance < nearestLocation) {
|
||||||
nearestLocation = distance
|
nearestLocation = distance
|
||||||
@@ -154,16 +122,16 @@ open class RouteModel () {
|
|||||||
distanceToStepEnd = 0F
|
distanceToStepEnd = 0F
|
||||||
val loc1 = Location(LocationManager.GPS_PROVIDER)
|
val loc1 = Location(LocationManager.GPS_PROVIDER)
|
||||||
val loc2 = Location(LocationManager.GPS_PROVIDER)
|
val loc2 = Location(LocationManager.GPS_PROVIDER)
|
||||||
loc1.longitude = polylineLocations[i][0]
|
loc1.longitude = route.waypoints[i][0]
|
||||||
loc1.latitude = polylineLocations[i][1]
|
loc1.latitude = route.waypoints[i][1]
|
||||||
loc2.longitude = polylineLocations[i+1][0]
|
loc2.longitude = route.waypoints[i + 1][0]
|
||||||
loc2.latitude = polylineLocations[i+1][1]
|
loc2.latitude = route.waypoints[i + 1][1]
|
||||||
bearing = loc1.bearingTo(loc2).absoluteValue
|
bearing = loc1.bearingTo(loc2).absoluteValue
|
||||||
for (j in i + 1..endShapeIndex) {
|
for (j in i + 1..endShapeIndex) {
|
||||||
loc1.longitude = polylineLocations[j - 1][0]
|
loc1.longitude = route.waypoints[j - 1][0]
|
||||||
loc1.latitude = polylineLocations[j - 1][1]
|
loc1.latitude = route.waypoints[j - 1][1]
|
||||||
loc2.longitude = polylineLocations[j][0]
|
loc2.longitude = route.waypoints[j][0]
|
||||||
loc2.latitude = polylineLocations[j][1]
|
loc2.latitude = route.waypoints[j][1]
|
||||||
distanceToStepEnd += loc1.distanceTo(loc2)
|
distanceToStepEnd += loc1.distanceTo(loc2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,8 +146,8 @@ open class RouteModel () {
|
|||||||
var nearestLocation = 100000.0f
|
var nearestLocation = 100000.0f
|
||||||
for (i in beginShapeIndex..endShapeIndex) {
|
for (i in beginShapeIndex..endShapeIndex) {
|
||||||
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
|
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
polylineLocation.longitude = polylineLocations[i][0]
|
polylineLocation.longitude = route.waypoints[i][0]
|
||||||
polylineLocation.latitude = polylineLocations[i][1]
|
polylineLocation.latitude = route.waypoints[i][1]
|
||||||
val distance: Float = location.distanceTo(polylineLocation)
|
val distance: Float = location.distanceTo(polylineLocation)
|
||||||
if (distance < nearestLocation) {
|
if (distance < nearestLocation) {
|
||||||
nearestLocation = distance
|
nearestLocation = distance
|
||||||
@@ -188,15 +156,21 @@ open class RouteModel () {
|
|||||||
return nearestLocation
|
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 {
|
fun travelLeftTime(): Double {
|
||||||
var timeLeft = 0.0
|
var timeLeft = 0.0
|
||||||
for (i in maneuverIndex + 1..<routingManeuvers.size) {
|
for (i in route.currentIndex + 1..<route.routingManeuvers.size) {
|
||||||
val maneuver = routingManeuvers[i]
|
val maneuver = route.routingManeuvers[i]
|
||||||
timeLeft += maneuver.getDouble("time")
|
timeLeft += maneuver.time
|
||||||
}
|
}
|
||||||
if (endIndex > 0) {
|
if (endIndex > 0) {
|
||||||
val maneuver = routingManeuvers[maneuverIndex]
|
val maneuver = route.currentManeuver()
|
||||||
val curTime = maneuver.getDouble("time")
|
val curTime = maneuver.time
|
||||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
||||||
val time = curTime * percent / 100
|
val time = curTime * percent / 100
|
||||||
timeLeft += time
|
timeLeft += time
|
||||||
@@ -206,8 +180,8 @@ open class RouteModel () {
|
|||||||
|
|
||||||
/** Returns the current [Step] left distance in km. */
|
/** Returns the current [Step] left distance in km. */
|
||||||
fun leftStepDistance(): Double {
|
fun leftStepDistance(): Double {
|
||||||
val maneuver = routingManeuvers[maneuverIndex]
|
val maneuver = route.routingManeuvers[route.currentIndex]
|
||||||
var leftDistance = maneuver.getDouble("length")
|
var leftDistance = maneuver.length
|
||||||
if (endIndex > 0) {
|
if (endIndex > 0) {
|
||||||
leftDistance = (distanceToStepEnd / 1000).toDouble()
|
leftDistance = (distanceToStepEnd / 1000).toDouble()
|
||||||
}
|
}
|
||||||
@@ -216,13 +190,13 @@ open class RouteModel () {
|
|||||||
|
|
||||||
fun travelLeftDistance(): Double {
|
fun travelLeftDistance(): Double {
|
||||||
var leftDistance = 0.0
|
var leftDistance = 0.0
|
||||||
for (i in maneuverIndex + 1..<routingManeuvers.size) {
|
for (i in route.currentIndex + 1..<route.routingManeuvers.size) {
|
||||||
val maneuver = routingManeuvers[i]
|
val maneuver = route.routingManeuvers[i]
|
||||||
leftDistance += maneuver.getDouble("length")
|
leftDistance += maneuver.length
|
||||||
}
|
}
|
||||||
if (endIndex > 0) {
|
if (endIndex > 0) {
|
||||||
val maneuver = routingManeuvers[maneuverIndex]
|
val maneuver = route.routingManeuvers[route.currentIndex]
|
||||||
val curDistance = maneuver.getDouble("length")
|
val curDistance = maneuver.length
|
||||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
||||||
val time = curDistance * percent / 100
|
val time = curDistance * percent / 100
|
||||||
leftDistance += time
|
leftDistance += time
|
||||||
@@ -239,11 +213,9 @@ open class RouteModel () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
|
route.clear()
|
||||||
navigating = false
|
navigating = false
|
||||||
polylineLocations = mutableListOf()
|
//maneuverIndex = 0
|
||||||
routingManeuvers = mutableListOf()
|
|
||||||
route = ""
|
|
||||||
maneuverIndex = 0
|
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
distanceToStepEnd = 0F
|
distanceToStepEnd = 0F
|
||||||
beginIndex = 0
|
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.data.Place_
|
import com.kouros.navigation.data.Place_
|
||||||
import com.kouros.navigation.utils.NavigationUtils
|
import com.kouros.navigation.utils.NavigationUtils
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
import io.objectbox.kotlin.boxFor
|
import io.objectbox.kotlin.boxFor
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -43,7 +44,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
val placeBox = boxStore.boxFor(Place::class)
|
val placeBox = boxStore.boxFor(Place::class)
|
||||||
pl.addAll(placeBox.all)
|
pl.addAll(placeBox.all)
|
||||||
for (place in pl) {
|
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)
|
val distance = repository.getRouteDistance(location, plLocation)
|
||||||
place.distance = distance.toFloat()
|
place.distance = distance.toFloat()
|
||||||
}
|
}
|
||||||
@@ -87,7 +88,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
address.address, 5) {
|
address.address, 5) {
|
||||||
for (adr in it) {
|
for (adr in it) {
|
||||||
if (addressLines.size > 1) {
|
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)
|
val distance = repository.getRouteDistance(currentLocation, plLocation)
|
||||||
contactList.add(
|
contactList.add(
|
||||||
Place(
|
Place(
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ import com.kouros.navigation.data.GeoJsonFeature
|
|||||||
import com.kouros.navigation.data.GeoJsonFeatureCollection
|
import com.kouros.navigation.data.GeoJsonFeatureCollection
|
||||||
import com.kouros.navigation.data.GeoJsonLineString
|
import com.kouros.navigation.data.GeoJsonLineString
|
||||||
import kotlinx.serialization.json.Json
|
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.toDegrees
|
||||||
import java.lang.Math.toRadians
|
import java.lang.Math.toRadians
|
||||||
import kotlin.math.asin
|
import kotlin.math.asin
|
||||||
@@ -14,8 +21,21 @@ import kotlin.math.cos
|
|||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.sin
|
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>> {
|
fun decodePolyline(encoded: String, vararg precisionOptional: Int): List<List<Double>> {
|
||||||
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
|
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
|
||||||
val factor = 10.0.pow(precision)
|
val factor = 10.0.pow(precision)
|
||||||
@@ -58,7 +78,7 @@ class NavigationUtils() {
|
|||||||
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
|
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
|
||||||
val featureCollection =
|
val featureCollection =
|
||||||
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
|
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
|
||||||
val jsonString = Json.Default.encodeToString(featureCollection)
|
val jsonString = Json.encodeToString(featureCollection)
|
||||||
return jsonString
|
return jsonString
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +124,9 @@ class NavigationUtils() {
|
|||||||
return snap
|
return snap
|
||||||
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
|
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateZoom(speed: Double?): Double {
|
fun calculateZoom(speed: Double?): Double {
|
||||||
if (speed == null) {
|
if (speed == null) {
|
||||||
return 18.0
|
return 18.0
|
||||||
}
|
}
|
||||||
@@ -120,12 +140,11 @@ class NavigationUtils() {
|
|||||||
else -> 11
|
else -> 11
|
||||||
}
|
}
|
||||||
return zoom.toDouble()
|
return zoom.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun location(latitude: Double, longitude: Double): Location {
|
fun location(latitude: Double, longitude: Double): Location {
|
||||||
val location = Location(LocationManager.GPS_PROVIDER)
|
val location = Location(LocationManager.GPS_PROVIDER)
|
||||||
location.longitude = longitude
|
location.longitude = longitude
|
||||||
location.latitude = latitude
|
location.latitude = latitude
|
||||||
return location
|
return location
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,24 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.13.1"
|
agp = "8.13.1"
|
||||||
|
androidSdkTurf = "6.0.1"
|
||||||
gradle = "8.13.1"
|
gradle = "8.13.1"
|
||||||
|
koinAndroid = "4.1.1"
|
||||||
koinAndroidxCompose = "4.1.1"
|
koinAndroidxCompose = "4.1.1"
|
||||||
|
koinComposeViewmodel = "4.1.1"
|
||||||
|
koinCore = "4.1.1"
|
||||||
kotlin = "2.2.21"
|
kotlin = "2.2.21"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.17.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.3.0"
|
junitVersion = "1.3.0"
|
||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
|
kotlinxSerializationJson = "1.9.0"
|
||||||
lifecycleRuntimeKtx = "2.9.4"
|
lifecycleRuntimeKtx = "2.9.4"
|
||||||
composeBom = "2025.11.00"
|
composeBom = "2025.11.00"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
material = "1.13.0"
|
material = "1.13.0"
|
||||||
carApp = "1.7.0"
|
carApp = "1.7.0"
|
||||||
#objectboxKotlin = "5.0.1"
|
objectboxKotlin = "5.0.1"
|
||||||
|
objectboxProcessor = "5.0.1"
|
||||||
ui = "1.9.4"
|
ui = "1.9.4"
|
||||||
material3 = "1.4.0"
|
material3 = "1.4.0"
|
||||||
runtimeLivedata = "1.9.4"
|
runtimeLivedata = "1.9.4"
|
||||||
@@ -24,6 +30,7 @@ runtime = "1.9.4"
|
|||||||
accompanist = "0.32.0"
|
accompanist = "0.32.0"
|
||||||
|
|
||||||
[libraries]
|
[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" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
|
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
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-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
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-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" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
androidx-car-app = { group = "androidx.car.app", name = "app", version.ref = "carApp" }
|
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-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" }
|
ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
|
||||||
maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre-compose" }
|
maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version.ref = "maplibre-compose" }
|
||||||
maplibre-composeMaterial3 = { module = "org.maplibre.compose:maplibre-compose-material3", version = "maplibre-composeMaterial3" }
|
maplibre-composeMaterial3 = { module = "org.maplibre.compose:maplibre-compose-material3", version = "maplibre-composeMaterial3" }
|
||||||
|
|||||||
Reference in New Issue
Block a user