Pan Mode
This commit is contained in:
@@ -17,8 +17,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionCode = 97
|
versionCode = 98
|
||||||
versionName = "0.2.3.97"
|
versionName = "0.2.3.98"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
|
||||||
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
|
||||||
@@ -45,7 +40,6 @@ import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
|||||||
import com.kouros.navigation.car.TextToSpeechManager
|
import com.kouros.navigation.car.TextToSpeechManager
|
||||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
|
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
|
||||||
import com.kouros.navigation.data.Constants.TILT
|
import com.kouros.navigation.data.Constants.TILT
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
@@ -59,7 +53,7 @@ import com.kouros.navigation.ui.navigation.NavigationSheet
|
|||||||
import com.kouros.navigation.ui.search.SearchSheet
|
import com.kouros.navigation.ui.search.SearchSheet
|
||||||
import com.kouros.navigation.ui.theme.NavigationTheme
|
import com.kouros.navigation.ui.theme.NavigationTheme
|
||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearingPositive
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
@@ -278,7 +272,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val bearing = if (currentLocation.hasBearing()) {
|
val bearing = if (currentLocation.hasBearing()) {
|
||||||
currentLocation.bearing.toDouble()
|
currentLocation.bearing.toDouble()
|
||||||
} else {
|
} else {
|
||||||
bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
bearingPositive(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||||
}
|
}
|
||||||
|
|
||||||
with(routeModel) {
|
with(routeModel) {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import org.junit.Test
|
|||||||
|
|
||||||
class OverpassTest {
|
class OverpassTest {
|
||||||
|
|
||||||
val location = Location(LocationManager.GPS_PROVIDER)
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `maxSpeed Schmalkaldener 30 `() {
|
fun `maxSpeed Schmalkaldener 30 `() {
|
||||||
val curLocation = location(11.582495, 48.186863)
|
val curLocation = location(11.582495, 48.186863)
|
||||||
@@ -31,7 +29,7 @@ class OverpassTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `maxSpeed Isarring 50 `() {
|
fun `maxSpeed Isarring 50 `() {
|
||||||
val curLocation = location(11.5989114, 48.1694783)
|
val curLocation = location(11.5989114, 48.1694783)
|
||||||
executeSpeedTest( curLocation, "Isarring", listOf("B2R"), 190, 50)
|
executeSpeedTest( curLocation, "Isarring", listOf("B2R"), 190, 60)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.kouros.data.BuildConfig
|
import com.kouros.data.BuildConfig
|
||||||
|
import com.kouros.navigation.data.Constants.a22
|
||||||
import com.kouros.navigation.data.Constants.a9
|
import com.kouros.navigation.data.Constants.a9
|
||||||
import com.kouros.navigation.data.Constants.a94
|
import com.kouros.navigation.data.Constants.a94
|
||||||
|
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
import com.kouros.navigation.data.Constants.ioannina
|
import com.kouros.navigation.data.Constants.ioannina
|
||||||
|
import com.kouros.navigation.data.Constants.isarring
|
||||||
import com.kouros.navigation.data.Constants.subislawa
|
import com.kouros.navigation.data.Constants.subislawa
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -104,8 +107,8 @@ class DeviceLocationManager(
|
|||||||
val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
val lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||||
if (lastLocation != null) {
|
if (lastLocation != null) {
|
||||||
if (setIndividualLocation) {
|
if (setIndividualLocation) {
|
||||||
onInitialLocation(homeVogelhart)
|
onInitialLocation(a22)
|
||||||
onLocationUpdate(homeVogelhart)
|
onLocationUpdate(a22)
|
||||||
} else {
|
} else {
|
||||||
onInitialLocation(lastLocation)
|
onInitialLocation(lastLocation)
|
||||||
onLocationUpdate(lastLocation)
|
onLocationUpdate(lastLocation)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import com.kouros.navigation.repository.SettingsRepository
|
|||||||
import com.kouros.navigation.utils.GeoUtils
|
import com.kouros.navigation.utils.GeoUtils
|
||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||||
|
import com.kouros.navigation.utils.bearingPositive
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
@@ -464,23 +465,25 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
*/
|
*/
|
||||||
private fun handleNavigationLocation(location: Location) {
|
private fun handleNavigationLocation(location: Location) {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
routeModel.updateLocation(location, navigationViewModel)
|
routeModel.updateLocation(location, navigationViewModel) // 30 ms
|
||||||
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations()) // 160 ms
|
||||||
val streetName = routeModel.currentStep().street
|
val streetName = routeModel.currentStep().street
|
||||||
if (checkLocationDeviation(location, snappedLocation, streetName)) {
|
if (checkLocationDeviation(location, snappedLocation, streetName)) { // 1 ms
|
||||||
if (routeModel.navState.arrived) return
|
if (routeModel.navState.arrived) return
|
||||||
if (guidanceAudio == 1) {
|
if (guidanceAudio == 1) {
|
||||||
handleGuidanceAudio()
|
handleGuidanceAudio()
|
||||||
}
|
}
|
||||||
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
checkTraffic(currentDate, snappedLocation)
|
checkTraffic(currentDate, snappedLocation) // 0 ms
|
||||||
updateSpeedCamera(snappedLocation)
|
updateSpeedCamera(snappedLocation) // 0 ms
|
||||||
checkRoute(currentDate, snappedLocation)
|
checkRoute(currentDate, snappedLocation) // 0 ms
|
||||||
updateNavigationScreen()
|
updateNavigationScreen() // 660 ms
|
||||||
checkArrival()
|
|
||||||
}
|
|
||||||
val endTime = System.currentTimeMillis() - startTime
|
val endTime = System.currentTimeMillis() - startTime
|
||||||
Log.d(TAG, "handleNavigationLocation: $endTime")
|
// Log.d(TAG, "updateNavigationScreen: $endTime")
|
||||||
|
checkArrival()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -498,9 +501,9 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
distance < MAXIMAL_SNAP_CORRECTION -> {
|
distance < MAXIMAL_SNAP_CORRECTION -> {
|
||||||
surfaceRenderer.updateLocation(snappedLocation, streetName)
|
surfaceRenderer.updateLocation(snappedLocation, streetName)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
surfaceRenderer.updateLocation(location, streetName)
|
surfaceRenderer.updateLocation(location, streetName)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -516,15 +519,14 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentStep = routeModel.route.currentStep()
|
val stepData = routeModel.currentStep() // 121 ms
|
||||||
val stepData = routeModel.currentStep()
|
|
||||||
|
|
||||||
navigationScreen.updateTrip(
|
navigationScreen.updateTrip( // 277 ms
|
||||||
isNavigating = routeModel.isNavigating(),
|
isNavigating = routeModel.isNavigating(),
|
||||||
isRerouting = false,
|
isRerouting = false,
|
||||||
hasArrived = routeModel.isArrival(),
|
hasArrived = routeModel.isArrival(),
|
||||||
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext),
|
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext), // 60 ms
|
||||||
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext),
|
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext), // 60 ms
|
||||||
destinations = mutableListOf(routeModel.getDestination()),
|
destinations = mutableListOf(routeModel.getDestination()),
|
||||||
steps = routeModel.getSteps(carContext),
|
steps = routeModel.getSteps(carContext),
|
||||||
stepRemainingDistance = routeModel.getDistance(),
|
stepRemainingDistance = routeModel.getDistance(),
|
||||||
@@ -539,7 +541,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
* Updates the trip information and notifies the listener with a new Trip object.
|
* Updates the trip information and notifies the listener with a new Trip object.
|
||||||
* This includes destination name, address, travel estimate, and loading status.
|
* This includes destination name, address, travel estimate, and loading status.
|
||||||
*/
|
*/
|
||||||
updateTrip(routeModel.getTrip(carContext))
|
updateTrip(routeModel.getTrip(carContext)) // 230 ms
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -594,6 +596,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
lastCameraSearch = 0
|
lastCameraSearch = 0
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
navigationScreen.navigationType = NavigationType.VIEW
|
navigationScreen.navigationType = NavigationType.VIEW
|
||||||
|
navigationScreen.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateTrip(trip: Trip) {
|
override fun updateTrip(trip: Trip) {
|
||||||
@@ -781,6 +784,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
private fun updateDistance(
|
private fun updateDistance(
|
||||||
location: Location,
|
location: Location,
|
||||||
) {
|
) {
|
||||||
|
synchronized(this) {
|
||||||
val updatedCameras = mutableListOf<Elements>()
|
val updatedCameras = mutableListOf<Elements>()
|
||||||
speedCameras.forEach {
|
speedCameras.forEach {
|
||||||
val plLocation =
|
val plLocation =
|
||||||
@@ -791,19 +795,20 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
}
|
}
|
||||||
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
|
||||||
val camera = sortedList.firstOrNull() ?: return
|
val camera = sortedList.firstOrNull() ?: return
|
||||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
val bearingRoute = surfaceRenderer.lastLocation.bearingPositive(location)
|
||||||
val bearingSpeedCamera = try {
|
val bearingSpeedCamera = try {
|
||||||
camera.tags.direction.toFloat()
|
camera.tags.direction.toFloat()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
0F
|
0F
|
||||||
}
|
}
|
||||||
if (camera.distance < 80 && (System.currentTimeMillis() - lastAlert > 5000)) {
|
if (camera.distance < 80 && (System.currentTimeMillis() - lastAlert > 10000)) {
|
||||||
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
|
if ((bearingSpeedCamera - bearingRoute).absoluteValue < 15.0) {
|
||||||
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
|
||||||
lastAlert = System.currentTimeMillis()
|
lastAlert = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a new route is needed based on the time since the last update.
|
* Checks if a new route is needed based on the time since the last update.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import com.kouros.navigation.data.Constants.TILT
|
|||||||
import com.kouros.navigation.data.DarkMode
|
import com.kouros.navigation.data.DarkMode
|
||||||
import com.kouros.navigation.data.ViewStyle
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearingPositive
|
||||||
import com.kouros.navigation.utils.calculateTilt
|
import com.kouros.navigation.utils.calculateTilt
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
import com.kouros.navigation.utils.duration
|
import com.kouros.navigation.utils.duration
|
||||||
@@ -46,6 +46,7 @@ import org.maplibre.compose.camera.CameraState
|
|||||||
import org.maplibre.compose.style.BaseStyle
|
import org.maplibre.compose.style.BaseStyle
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,15 +226,40 @@ class SurfaceRenderer(
|
|||||||
*/
|
*/
|
||||||
override fun onScroll(distanceX: Float, distanceY: Float) {
|
override fun onScroll(distanceX: Float, distanceY: Float) {
|
||||||
synchronized(this@SurfaceRenderer) {
|
synchronized(this@SurfaceRenderer) {
|
||||||
|
val currentCamera = cameraPosition.value ?: return@synchronized
|
||||||
|
val zoom = currentCamera.zoom
|
||||||
|
val bearing = currentCamera.bearing
|
||||||
|
|
||||||
|
// MapLibre typically uses 512px tiles.
|
||||||
|
// At zoom level z, the world (360 degrees) is 512 * 2^z pixels wide.
|
||||||
|
val pixelsPerDegreeLon = (512.0 * 2.0.pow(zoom)) / 360.0
|
||||||
|
|
||||||
|
// Latitude correction: In Mercator, the vertical scale is stretched by 1/cos(lat).
|
||||||
|
val latRad = lastLocation.latitude * PI / 180.0
|
||||||
|
val pixelsPerDegreeLat = pixelsPerDegreeLon / cos(latRad)
|
||||||
|
|
||||||
|
// Rotation compensation (bearing is degrees clockwise from North)
|
||||||
|
val bearingRad = bearing * PI / 180.0
|
||||||
|
val cosB = cos(bearingRad)
|
||||||
|
val sinB = sin(bearingRad)
|
||||||
|
|
||||||
|
// Rotate screen-space scroll to map-space scroll
|
||||||
|
val rotatedDx = distanceX * cosB - distanceY * sinB
|
||||||
|
val rotatedDy = distanceX * sinB + distanceY * cosB
|
||||||
|
|
||||||
viewStyle = ViewStyle.PAN_VIEW
|
viewStyle = ViewStyle.PAN_VIEW
|
||||||
if (distanceX != 0.0F) {
|
|
||||||
lastLocation.longitude += (distanceX / 1000) / cameraPosition.value!!.zoom
|
// Update location based on rotated scroll distances and calculated factors
|
||||||
}
|
lastLocation.longitude += (rotatedDx / pixelsPerDegreeLon)
|
||||||
if (distanceY != 0.0F) {
|
lastLocation.latitude -= (rotatedDy / pixelsPerDegreeLat)
|
||||||
lastLocation.latitude += (distanceY / 1000) / cameraPosition.value!!.zoom
|
|
||||||
}
|
|
||||||
val pos = Position(lastLocation.longitude, lastLocation.latitude)
|
val pos = Position(lastLocation.longitude, lastLocation.latitude)
|
||||||
updateCameraPosition( target = pos)
|
updateCameraPosition(
|
||||||
|
bearing = bearing,
|
||||||
|
zoom = zoom,
|
||||||
|
target = pos,
|
||||||
|
tilt = tilt
|
||||||
|
)
|
||||||
navigationSession.invalidateNavigationScreen()
|
navigationSession.invalidateNavigationScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,7 +400,7 @@ class SurfaceRenderer(
|
|||||||
if (location.hasBearing()) {
|
if (location.hasBearing()) {
|
||||||
location.bearing.toDouble()
|
location.bearing.toDouble()
|
||||||
} else {
|
} else {
|
||||||
bearing(
|
bearingPositive(
|
||||||
lastLocation,
|
lastLocation,
|
||||||
location,
|
location,
|
||||||
cameraPosition.value!!.bearing
|
cameraPosition.value!!.bearing
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ fun DrawNavigationImages(
|
|||||||
if (speed != null) {
|
if (speed != null) {
|
||||||
CurrentSpeed(width, height, speed, maxSpeed)
|
CurrentSpeed(width, height, speed, maxSpeed)
|
||||||
}
|
}
|
||||||
if (speed != null && maxSpeed > 0) { // && (speed * 3.6) > maxSpeed) {
|
if (speed != null && maxSpeed > 0) {
|
||||||
MaxSpeed(width, height, maxSpeed, speed)
|
MaxSpeed(width, height, maxSpeed, speed)
|
||||||
}
|
}
|
||||||
//DebugInfo(width, height, lat!!)
|
//DebugInfo(width, height, lat!!)
|
||||||
@@ -507,7 +507,7 @@ private fun MaxSpeed(
|
|||||||
val textLayoutSpeed = remember(speed) {
|
val textLayoutSpeed = remember(speed) {
|
||||||
textMeasurerSpeed.measure(speed, styleSpeed)
|
textMeasurerSpeed.measure(speed, styleSpeed)
|
||||||
}
|
}
|
||||||
val signColor = if (curSpeed * 3.6 > maxSpeed) {
|
val signColor = if (curSpeed * 3.6 > (maxSpeed + 3)) {
|
||||||
Color.Red
|
Color.Red
|
||||||
} else {
|
} else {
|
||||||
Color.Green
|
Color.Green
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ class Simulation {
|
|||||||
longitude = point[0]
|
longitude = point[0]
|
||||||
bearing = curBearing
|
bearing = curBearing
|
||||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||||
speed = 10.0f
|
speed = 9.0f
|
||||||
time = System.currentTimeMillis()
|
time = System.currentTimeMillis()
|
||||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
}
|
}
|
||||||
// Update your app's state as if a real GPS update occurred
|
// Update your app's state as if a real GPS update occurred
|
||||||
updateLocation(fakeLocation)
|
updateLocation(fakeLocation)
|
||||||
// Wait before moving to the next point (e.g., every 1 second)
|
// Wait before moving to the next point (e.g., every 1 second)
|
||||||
delay(1000)
|
delay(500)
|
||||||
lastLocation = fakeLocation
|
lastLocation = fakeLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ open class NavigationScreen(
|
|||||||
|
|
||||||
repository.tripSuggestionFlow.asLiveData().observe(this, Observer {
|
repository.tripSuggestionFlow.asLiveData().observe(this, Observer {
|
||||||
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
||||||
//navigationViewModel.recentPlacesFlow(carContext, surfaceRenderer.lastLocation ).asLiveData().observe(this, ::test)
|
|
||||||
tripSuggestion = it
|
tripSuggestion = it
|
||||||
})
|
})
|
||||||
repository.alternativeRoutesFlow.asLiveData().observe(this, Observer {
|
repository.alternativeRoutesFlow.asLiveData().observe(this, Observer {
|
||||||
@@ -142,10 +141,10 @@ open class NavigationScreen(
|
|||||||
createAction(
|
createAction(
|
||||||
carContext,
|
carContext,
|
||||||
R.drawable.ic_close_white_24dp,
|
R.drawable.ic_close_white_24dp,
|
||||||
0,
|
0
|
||||||
{ stopNavigation() })
|
) { stopNavigation() }
|
||||||
)
|
)
|
||||||
return NavigationTemplate.Builder()
|
val navigationTemplate = NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(
|
.setNavigationInfo(
|
||||||
getRoutingInfo()
|
getRoutingInfo()
|
||||||
)
|
)
|
||||||
@@ -167,6 +166,7 @@ open class NavigationScreen(
|
|||||||
)
|
)
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(backGroundColor)
|
||||||
.build()
|
.build()
|
||||||
|
return navigationTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -490,8 +490,6 @@ open class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
||||||
*/
|
*/
|
||||||
@@ -523,7 +521,6 @@ open class NavigationScreen(
|
|||||||
this.junctionImage = junctionImage
|
this.junctionImage = junctionImage
|
||||||
this.backGroundColor = backGroundColor
|
this.backGroundColor = backGroundColor
|
||||||
this.message = message
|
this.message = message
|
||||||
|
|
||||||
navigationType = NavigationType.NAVIGATION
|
navigationType = NavigationType.NAVIGATION
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,8 +128,10 @@ object Constants {
|
|||||||
val ioannina = location( 20.826237, 39.690174)
|
val ioannina = location( 20.826237, 39.690174)
|
||||||
val subislawa = location(18.570808, 54.420647)
|
val subislawa = location(18.570808, 54.420647)
|
||||||
val a94 = location(11.872097,48.163449)
|
val a94 = location(11.872097,48.163449)
|
||||||
|
|
||||||
val a9 = location(11.621556, 48.204402,)
|
val a9 = location(11.621556, 48.204402,)
|
||||||
|
|
||||||
|
val a22 = location(10.845749, 45.602507)
|
||||||
|
val isarring = location( 11.609696, 48.155116)
|
||||||
const val NEXT_STEP_THRESHOLD = 500.0
|
const val NEXT_STEP_THRESHOLD = 500.0
|
||||||
|
|
||||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||||
|
|||||||
@@ -11,59 +11,59 @@ import java.net.URL
|
|||||||
|
|
||||||
class Overpass {
|
class Overpass {
|
||||||
|
|
||||||
|
private val gson = GsonBuilder().serializeNulls().create()
|
||||||
|
|
||||||
var overpassUrl = if (BuildConfig.DEBUG)
|
var overpassUrl = if (BuildConfig.DEBUG)
|
||||||
"http://192.168.1.37/api/interpreter"
|
"http://192.168.1.37/api/interpreter"
|
||||||
else
|
else
|
||||||
"https://kouros-online.de/api/interpreter"
|
"https://kouros-online.de/api/interpreter"
|
||||||
|
|
||||||
|
val destination = "[!destination][highway!=\"motorway_link\"]"
|
||||||
fun getSpeedLimit(radius: Float, linestring: String, street: String, roadNumbers: List<String>): List<Elements> {
|
fun getSpeedLimit(radius: Float, linestring: String, street: String, roadNumbers: List<String>): List<Elements> {
|
||||||
val streetName = if (street.length > 10) {
|
|
||||||
street.substring(0, 10)
|
val wayAround = "way[maxspeed](around:$radius,$linestring)"
|
||||||
} else {
|
val searchClauses = mutableListOf<String>()
|
||||||
street
|
|
||||||
}
|
// 1. Search by street name (fuzzy match with first 10 characters)
|
||||||
val name = if (streetName.isEmpty()) {
|
val streetPrefix = street.take(10)
|
||||||
""
|
if (streetPrefix.isNotEmpty()) {
|
||||||
} else {
|
searchClauses.add("$wayAround[name~\"^$streetPrefix\"]")
|
||||||
"[name~\"^${streetName}\"]"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val regex = Regex("""\d+|\D+""")
|
// 2. Search by road numbers (ref or int_ref)
|
||||||
val search = "way[maxspeed](around:$radius,$linestring)$name[!destination][highway!=\"motorway_link\"]"
|
val partRegex = Regex("""\d+|\D+""")
|
||||||
var waySearch = search
|
roadNumbers.forEach { number ->
|
||||||
for ((index, r) in roadNumbers.withIndex()) {
|
val parts = partRegex.findAll(number).map { it.value.trim() }.filter { it.isNotEmpty() }.toList()
|
||||||
val result = regex.findAll(r).map { it.groupValues.first() }.toList()
|
if (parts.isNotEmpty()) {
|
||||||
if (index > 0) {
|
// Construct ref value: e.g., "A1" -> "A 1", "E30" -> "E 30"
|
||||||
waySearch = waySearch.plus(";").plus(search)
|
val refValue = if (parts.size > 1) "${parts[0]} ${parts.drop(1).joinToString("")}" else parts[0]
|
||||||
}
|
val tag = if (number.startsWith("E", ignoreCase = true)) "int_ref" else "ref"
|
||||||
var refValue = result.first().trim().plus(" ")
|
searchClauses.add("$wayAround$destination[$tag~\"$refValue\"]")
|
||||||
for (res in result.subList(1, result.size)) {
|
|
||||||
refValue = refValue.plus(res)
|
|
||||||
}
|
|
||||||
// International reference starts with "E"
|
|
||||||
waySearch = if (r.first().toString() == "E") {
|
|
||||||
waySearch.plus("[int_ref~\"${refValue}\"]")
|
|
||||||
} else {
|
|
||||||
waySearch.plus("[ref~\"${refValue}\"]")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
|
||||||
httpURLConnection.requestMethod = "POST"
|
// 3. Fallback to searching everything if no specific filters were added
|
||||||
httpURLConnection.setRequestProperty(
|
if (searchClauses.isEmpty()) {
|
||||||
"Accept",
|
searchClauses.add(wayAround)
|
||||||
"application/json"
|
}
|
||||||
)
|
|
||||||
httpURLConnection.setDoOutput(true);
|
|
||||||
// define search query
|
|
||||||
val searchQuery = """
|
val searchQuery = """
|
||||||
|[out:json];
|
|[out:json];
|
||||||
|(
|
|(
|
||||||
| $waySearch;
|
| ${searchClauses.joinToString(";")};
|
||||||
|);
|
|);
|
||||||
|out body geom;
|
|out body geom;
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
return overpassApi(httpURLConnection, searchQuery)
|
|
||||||
|
Log.d("OverpassApi", "Overpass Query: $searchQuery")
|
||||||
|
|
||||||
|
val connection = (URL(overpassUrl).openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "POST"
|
||||||
|
setRequestProperty("Accept", "application/json")
|
||||||
|
doOutput = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return overpassApi(connection, searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -74,16 +74,7 @@ class Overpass {
|
|||||||
radius: Double
|
radius: Double
|
||||||
): List<Elements> {
|
): List<Elements> {
|
||||||
val boundingBox = getBoundingBox(location.latitude, location.longitude, radius)
|
val boundingBox = getBoundingBox(location.latitude, location.longitude, radius)
|
||||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
|
||||||
httpURLConnection.requestMethod = "POST"
|
|
||||||
// node["highway"="speed_camera"]
|
|
||||||
// node[amenity=$category]
|
|
||||||
httpURLConnection.setDoOutput(true);
|
|
||||||
httpURLConnection.setRequestProperty(
|
|
||||||
"Accept",
|
|
||||||
"application/json"
|
|
||||||
)
|
|
||||||
// define search query
|
|
||||||
val searchQuery = """
|
val searchQuery = """
|
||||||
|[out:json];
|
|[out:json];
|
||||||
|(
|
|(
|
||||||
@@ -93,32 +84,40 @@ class Overpass {
|
|||||||
|(._;>;);
|
|(._;>;);
|
||||||
|out body geom;
|
|out body geom;
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
return overpassApi(httpURLConnection, searchQuery)
|
|
||||||
|
val connection = (URL(overpassUrl).openConnection() as HttpURLConnection).apply {
|
||||||
|
requestMethod = "POST"
|
||||||
|
setRequestProperty("Accept", "application/json")
|
||||||
|
doOutput = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String): List<Elements> {
|
return overpassApi(connection, searchQuery)
|
||||||
try {
|
}
|
||||||
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
|
|
||||||
outputStreamWriter.write(searchQuery)
|
private fun overpassApi(connection: HttpURLConnection, searchQuery: String): List<Elements> {
|
||||||
outputStreamWriter.flush()
|
return try {
|
||||||
// Check if the connection is successful
|
connection.outputStream.use { os ->
|
||||||
httpURLConnection.requestMethod = "POST"
|
OutputStreamWriter(os).use { writer ->
|
||||||
val responseCode = httpURLConnection.responseCode
|
writer.write(searchQuery)
|
||||||
|
writer.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseCode = connection.responseCode
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
val response = httpURLConnection.inputStream.bufferedReader()
|
val response = connection.inputStream.bufferedReader().use { it.readText() }
|
||||||
.use { it.readText() } // defaults to UTF-8
|
|
||||||
if (response.startsWith("<?xml")) {
|
if (response.startsWith("<?xml")) {
|
||||||
|
Log.w("OverpassApi", "Received XML instead of JSON")
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
gson.fromJson(response, Amenity::class.java).elements
|
||||||
val overpass = gson.fromJson(response, Amenity::class.java)
|
|
||||||
return overpass.elements
|
|
||||||
} else {
|
} else {
|
||||||
Log.e("OverpassApi", responseCode.toString())
|
Log.e("OverpassApi", "Error code: $responseCode")
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("OverpassApi", e.toString())
|
Log.e("OverpassApi", "Exception in Overpass API call", e)
|
||||||
|
emptyList()
|
||||||
}
|
}
|
||||||
return emptyList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ data class Instruction(
|
|||||||
val point: Point,
|
val point: Point,
|
||||||
val pointIndex: Int,
|
val pointIndex: Int,
|
||||||
val possibleCombineWithNext: Boolean,
|
val possibleCombineWithNext: Boolean,
|
||||||
val roadNumbers: List<String>,
|
val roadNumbers: List<String> = emptyList(),
|
||||||
val routeOffsetInMeters: Int,
|
val routeOffsetInMeters: Int,
|
||||||
val signpostText: String,
|
val signpostText: String,
|
||||||
val street: String? = "",
|
val street: String? = "",
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
}
|
}
|
||||||
if (useLocal) {
|
if (useLocal) {
|
||||||
return fetchUrl(
|
return fetchUrl(
|
||||||
"http://192.168.1.37/tomtom_routing.json",
|
//"http://192.168.1.37/tomtom_routing.json",
|
||||||
|
"http://192.168.1.37/verona.json",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.kouros.navigation.model
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
@@ -22,6 +21,7 @@ import com.kouros.navigation.data.nominatim.SearchResult
|
|||||||
import com.kouros.navigation.data.overpass.ElementSearch
|
import com.kouros.navigation.data.overpass.ElementSearch
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
import com.kouros.navigation.data.overpass.Overpass
|
import com.kouros.navigation.data.overpass.Overpass
|
||||||
|
import com.kouros.navigation.utils.bearingPositive
|
||||||
import com.kouros.navigation.utils.countryCodeSpeedLimit
|
import com.kouros.navigation.utils.countryCodeSpeedLimit
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
@@ -390,6 +390,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
* Posts sorted results to speedCameras LiveData.
|
* Posts sorted results to speedCameras LiveData.
|
||||||
*/
|
*/
|
||||||
fun getSpeedCameras(location: Location, radius: Double) {
|
fun getSpeedCameras(location: Location, radius: Double) {
|
||||||
|
synchronized(this) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
|
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
|
||||||
val distAmenities = mutableListOf<Elements>()
|
val distAmenities = mutableListOf<Elements>()
|
||||||
@@ -404,6 +405,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
speedCameras.postValue(sortedList)
|
speedCameras.postValue(sortedList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||||
@@ -415,9 +417,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
countryCode: String,
|
countryCode: String,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
synchronized(this) {
|
||||||
maxSpeed.postValue(calculateSpeedLimit(location, routeBearing, countryCode))
|
maxSpeed.postValue(calculateSpeedLimit(location, routeBearing, countryCode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||||
@@ -428,31 +432,30 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
val search = mutableListOf<ElementSearch>()
|
val search = mutableListOf<ElementSearch>()
|
||||||
|
|
||||||
speedElements.filter { it.type == "way"}.forEach {
|
speedElements.filter { it.type == "way"}.forEach {
|
||||||
var streetBearingSum = 0F
|
|
||||||
var streetBearingAvg = 0F
|
|
||||||
var distance = 0F
|
var distance = 0F
|
||||||
var maxDistance = 1000F
|
var maxDistance = 1000F
|
||||||
|
var geometryFirstLocation = location(0.0, 0.0)
|
||||||
|
var geometryLastLocation = location(0.0, 0.0)
|
||||||
for ((geoIndex, geo) in it.geometry.withIndex()) {
|
for ((geoIndex, geo) in it.geometry.withIndex()) {
|
||||||
|
if (geoIndex == 0) {
|
||||||
|
geometryFirstLocation= location(geo.lon, geo.lat)
|
||||||
|
}
|
||||||
|
if (geoIndex == it.geometry.size-1) {
|
||||||
|
geometryLastLocation = location(geo.lon, geo.lat)
|
||||||
|
}
|
||||||
val geometryLocation = location(geo.lon, geo.lat)
|
val geometryLocation = location(geo.lon, geo.lat)
|
||||||
distance = geometryLocation.distanceTo(location)
|
distance = geometryLocation.distanceTo(location)
|
||||||
if (distance < maxDistance) {
|
if (distance < maxDistance) {
|
||||||
maxDistance = distance
|
maxDistance = distance
|
||||||
}
|
}
|
||||||
if (geoIndex > 0) {
|
|
||||||
val prevLocation =
|
|
||||||
location(it.geometry[geoIndex - 1].lon, it.geometry[geoIndex - 1].lat)
|
|
||||||
val streetBearing = prevLocation.bearingTo(geometryLocation).absoluteValue
|
|
||||||
streetBearingSum = (streetBearingSum + streetBearing)
|
|
||||||
streetBearingAvg = streetBearingSum / geoIndex
|
|
||||||
}
|
}
|
||||||
}
|
val streetBearing = geometryFirstLocation.bearingPositive(geometryLastLocation)
|
||||||
val bearing = calculateBearing(it, streetBearingAvg, routeBearing)
|
if (isBearingValid(it, streetBearing, routeBearing)) {
|
||||||
if (bearing < SPEED_BEARING_DEVIATION) {
|
|
||||||
search.add(
|
search.add(
|
||||||
ElementSearch(
|
ElementSearch(
|
||||||
it,
|
it,
|
||||||
maxDistance.toDouble(),
|
maxDistance.toDouble(),
|
||||||
streetBearingAvg.absoluteValue
|
streetBearing.absoluteValue
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -460,7 +463,6 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
val result = search.sortedWith(compareBy<ElementSearch> { it.distance }.thenByDescending { it.bearing })
|
val result = search.sortedWith(compareBy<ElementSearch> { it.distance }.thenByDescending { it.bearing })
|
||||||
if (result.isNotEmpty()) {
|
if (result.isNotEmpty()) {
|
||||||
element = result.first().element
|
element = result.first().element
|
||||||
//Log.d("NavigationViewModel", "Distance: ${result.first().distance} Bearing: ${result.first().bearing} RouteBearing $routeBearing")
|
|
||||||
speed = if (element.tags.maxspeed == "none" && element.tags.highway == "motorway") {
|
speed = if (element.tags.maxspeed == "none" && element.tags.highway == "motorway") {
|
||||||
countryCodeSpeedLimit(countryCode)
|
countryCodeSpeedLimit(countryCode)
|
||||||
} else {
|
} else {
|
||||||
@@ -470,11 +472,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
return speed
|
return speed
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateBearing(element: Elements, streetBearingAvg : Float, routeBearing: Float) : Float {
|
private fun isBearingValid(element: Elements, streetBearing : Float, routeBearing: Float) : Boolean {
|
||||||
return if (element.tags.oneway.isNotEmpty()) {
|
return if (element.tags.oneway.isNotEmpty()) {
|
||||||
(streetBearingAvg - routeBearing.absoluteValue).absoluteValue
|
(streetBearing - routeBearing.absoluteValue) < SPEED_BEARING_DEVIATION
|
||||||
} else {
|
} else {
|
||||||
0F
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,12 +490,15 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
roadNumbers: List<String>
|
roadNumbers: List<String>
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
synchronized(this) {
|
||||||
val lineString = "${location.latitude},${location.longitude}"
|
val lineString = "${location.latitude},${location.longitude}"
|
||||||
val elements = Overpass().getSpeedLimit(SPEED_UPDATE_DISTANCE, lineString, street, roadNumbers)
|
val elements =
|
||||||
|
Overpass().getSpeedLimit(SPEED_UPDATE_DISTANCE, lineString, street, roadNumbers)
|
||||||
speedElements.clear()
|
speedElements.clear()
|
||||||
speedElements.addAll(elements)
|
speedElements.addAll(elements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_NATIVE
|
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_NATIVE
|
||||||
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_PROJECTION
|
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_PROJECTION
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.NavigationState
|
import com.kouros.navigation.data.NavigationState
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.kouros.navigation.utils
|
package com.kouros.navigation.utils
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import org.maplibre.geojson.FeatureCollection
|
import org.maplibre.geojson.FeatureCollection
|
||||||
|
|||||||
@@ -108,13 +108,12 @@ fun calculateTilt(viewStyle: ViewStyle, newZoom: Double, tilt: Double): Double =
|
|||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double): Double {
|
fun bearingPositive(fromLocation: Location, toLocation: Location, oldBearing: Double): Double {
|
||||||
val distance = fromLocation.distanceTo(toLocation)
|
val distance = fromLocation.distanceTo(toLocation)
|
||||||
if (distance < 1.0) {
|
if (distance < 1.0) {
|
||||||
return oldBearing
|
return oldBearing
|
||||||
}
|
}
|
||||||
val bearing = fromLocation.bearingTo(toLocation).toInt().toDouble()
|
return fromLocation.bearingPositive(toLocation).toInt().toDouble()
|
||||||
return bearing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun location(longitude: Double, latitude: Double): Location {
|
fun location(longitude: Double, latitude: Double): Location {
|
||||||
@@ -124,6 +123,10 @@ fun location(longitude: Double, latitude: Double): Location {
|
|||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Location.bearingPositive(locationTo: Location): Float {
|
||||||
|
return (this.bearingTo (locationTo) + 360) % 360
|
||||||
|
}
|
||||||
|
|
||||||
fun formatDateTime(time: Long): String {
|
fun formatDateTime(time: Long): String {
|
||||||
val dateFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
val dateFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||||
val dateTime = LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC)
|
val dateTime = LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC)
|
||||||
|
|||||||
+18
-18
@@ -1,13 +1,13 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.1.1"
|
agp = "9.2.0"
|
||||||
androidGpxParser = "2.3.1"
|
androidGpxParser = "2.3.1"
|
||||||
androidSdkTurf = "6.0.1"
|
androidSdkTurf = "6.0.1"
|
||||||
datastore = "1.2.1"
|
datastore = "1.2.1"
|
||||||
gradle = "9.1.1"
|
gradle = "9.2.0"
|
||||||
koinAndroid = "4.2.0"
|
koinAndroid = "4.2.1"
|
||||||
koinAndroidxCompose = "4.2.0"
|
koinAndroidxCompose = "4.2.1"
|
||||||
koinComposeViewmodel = "4.2.0"
|
koinComposeViewmodel = "4.2.1"
|
||||||
koinCore = "4.2.0"
|
koinCore = "4.2.1"
|
||||||
kotlin = "2.3.20"
|
kotlin = "2.3.20"
|
||||||
coreKtx = "1.18.0"
|
coreKtx = "1.18.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
@@ -15,7 +15,7 @@ junitVersion = "1.3.0"
|
|||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
kotlinxSerializationJson = "1.10.0"
|
kotlinxSerializationJson = "1.10.0"
|
||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
composeBom = "2026.03.01"
|
composeBom = "2026.04.01"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
material = "1.13.0"
|
material = "1.13.0"
|
||||||
carApp = "1.7.0"
|
carApp = "1.7.0"
|
||||||
@@ -26,21 +26,21 @@ mockitoKotlin = "6.3.0"
|
|||||||
rules = "1.7.0"
|
rules = "1.7.0"
|
||||||
runner = "1.7.0"
|
runner = "1.7.0"
|
||||||
material3 = "1.4.0"
|
material3 = "1.4.0"
|
||||||
runtimeLivedata = "1.10.6"
|
runtimeLivedata = "1.11.0"
|
||||||
foundation = "1.10.6"
|
foundation = "1.11.0"
|
||||||
maplibre-compose = "0.12.1"
|
maplibre-compose = "0.12.1"
|
||||||
playServicesLocation = "21.3.0"
|
playServicesLocation = "21.3.0"
|
||||||
runtime = "1.10.6"
|
runtime = "1.11.0"
|
||||||
accompanist = "0.37.3"
|
accompanist = "0.37.3"
|
||||||
uiVersion = "1.10.6"
|
uiVersion = "1.11.0"
|
||||||
uiText = "1.10.6"
|
uiText = "1.11.0"
|
||||||
navigationCompose = "2.9.7"
|
navigationCompose = "2.9.8"
|
||||||
uiToolingPreview = "1.10.6"
|
uiToolingPreview = "1.11.0"
|
||||||
uiTooling = "1.10.6"
|
uiTooling = "1.11.0"
|
||||||
material3WindowSizeClass = "1.4.0"
|
material3WindowSizeClass = "1.4.0"
|
||||||
uiGraphics = "1.10.6"
|
uiGraphics = "1.11.0"
|
||||||
window = "1.5.1"
|
window = "1.5.1"
|
||||||
foundationLayout = "1.10.6"
|
foundationLayout = "1.11.0"
|
||||||
datastorePreferences = "1.2.1"
|
datastorePreferences = "1.2.1"
|
||||||
datastoreCore = "1.2.1"
|
datastoreCore = "1.2.1"
|
||||||
monitor = "1.8.0"
|
monitor = "1.8.0"
|
||||||
@@ -48,7 +48,7 @@ robolectric = "4.16.1"
|
|||||||
truth = "1.4.5"
|
truth = "1.4.5"
|
||||||
testCore = "1.7.0"
|
testCore = "1.7.0"
|
||||||
archCoreTesting = "2.2.0"
|
archCoreTesting = "2.2.0"
|
||||||
kotlinxCoroutinesTest = "1.10.1"
|
kotlinxCoroutinesTest = "1.10.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-gpx-parser = { module = "com.github.ticofab:android-gpx-parser", version.ref = "androidGpxParser" }
|
android-gpx-parser = { module = "com.github.ticofab:android-gpx-parser", version.ref = "androidGpxParser" }
|
||||||
|
|||||||
Reference in New Issue
Block a user