Diverse Änderungen
This commit is contained in:
@@ -124,7 +124,7 @@ object Constants {
|
||||
val homeVogelhart = location(11.5793748, 48.185749)
|
||||
val homeHohenwaldeck = location( 11.594322, 48.1164817)
|
||||
|
||||
const val NEXT_STEP_THRESHOLD = 500.0
|
||||
const val NEXT_STEP_THRESHOLD = 300.0
|
||||
|
||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||
|
||||
|
||||
@@ -33,59 +33,87 @@ import org.maplibre.geojson.FeatureCollection
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* ViewModel for navigation-related data operations.
|
||||
* Handles route calculation, place search, traffic information, and local data persistence.
|
||||
*/
|
||||
class NavigationViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
|
||||
/** LiveData containing the calculated route JSON string */
|
||||
val route: MutableLiveData<String> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing categorized traffic incidents map */
|
||||
val traffic: MutableLiveData<Map<String, String>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
|
||||
/** LiveData containing a preview route JSON string for route preview screens */
|
||||
val previewRoute: MutableLiveData<String> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing the most recent place used for navigation */
|
||||
val recentPlace: MutableLiveData<Place> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing list of recent navigation destinations */
|
||||
val places: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing list of favorite saved places */
|
||||
val favorites: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing search results from Nominatim geocoding */
|
||||
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing the best matching place from address search */
|
||||
val placeLocation: MutableLiveData<SearchResult> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing contacts with addresses */
|
||||
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing POI elements from Overpass API */
|
||||
val elements: MutableLiveData<List<Elements>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing speed camera locations */
|
||||
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing current road speed limit */
|
||||
val maxSpeed: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing selected routing engine index */
|
||||
val routingEngine: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData indicating whether permission is granted */
|
||||
val permissionGranted: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the most recent place from ObjectBox and calculates its distance.
|
||||
* Posts the result to recentPlace LiveData if distance > 1km.
|
||||
*/
|
||||
fun loadRecentPlace(location: Location, carOrientation: Float, context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -116,6 +144,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all recent places from ObjectBox and calculates distances.
|
||||
* Posts the sorted list to places LiveData.
|
||||
*/
|
||||
fun loadRecentPlaces(context: Context, location: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -146,6 +178,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads favorite places from ObjectBox and calculates distances.
|
||||
* Posts the sorted list to favorites LiveData.
|
||||
*/
|
||||
fun loadFavorites(context: Context, location: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -175,6 +211,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a route between current location and destination.
|
||||
* Posts the route JSON to route LiveData.
|
||||
*/
|
||||
fun loadRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
@@ -198,6 +238,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches traffic incident data and categorizes by severity.
|
||||
* Posts categorized traffic map to traffic LiveData.
|
||||
*/
|
||||
fun loadTraffic(context: Context, currentLocation: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -216,6 +260,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorizes traffic incidents by type (queuing, stationary, slow, heavy, roadworks).
|
||||
* @param data Raw traffic GeoJSON string
|
||||
* @return Map of incident type to GeoJSON FeatureCollection
|
||||
*/
|
||||
private fun rebuildTraffic(data: String): Map<String, String> {
|
||||
val featureCollection = FeatureCollection.fromJson(data)
|
||||
val incidents = mutableMapOf<String, String>()
|
||||
@@ -238,6 +287,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return incidents
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a preview route for route preview screen.
|
||||
* Posts the route JSON to previewRoute LiveData.
|
||||
*/
|
||||
fun loadPreviewRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
@@ -261,6 +314,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads device contacts with addresses and converts to Place objects.
|
||||
* Posts results to contactAddress LiveData.
|
||||
*/
|
||||
fun loadContacts(context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val contactList = mutableListOf<Place>()
|
||||
@@ -288,7 +345,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the closest matching address for a search query.
|
||||
* Posts the best result to placeLocation LiveData.
|
||||
*/
|
||||
fun findAddress(search: String, location: Location) {
|
||||
var sortedList: List<SearchResult>
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
@@ -310,6 +370,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for places using Nominatim geocoding API.
|
||||
* Posts sorted results to searchPlaces LiveData.
|
||||
*/
|
||||
fun searchPlaces(search: String, location: Location) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val placesJson = repository.searchPlaces(search, location)
|
||||
@@ -330,6 +394,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs reverse geocoding to get street name from coordinates.
|
||||
* @return Street name or empty string
|
||||
*/
|
||||
fun reverseAddress(location: Location): String {
|
||||
val address = repository.reverseAddress(location)
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
@@ -337,6 +405,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return place.address.road
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for nearby amenities of a specific category.
|
||||
* Posts sorted results to elements LiveData.
|
||||
*/
|
||||
fun getAmenities(category: String, location: Location) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
|
||||
@@ -353,6 +425,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for speed cameras within a radius.
|
||||
* Posts sorted results to speedCameras LiveData.
|
||||
*/
|
||||
fun getSpeedCameras(location: Location, radius: Double) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
|
||||
@@ -369,6 +445,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||
* Posts speed limit to maxSpeed LiveData.
|
||||
*/
|
||||
fun getMaxSpeed(location: Location, street: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val levenshtein = Levenshtein()
|
||||
@@ -387,11 +467,18 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a place as a favorite in ObjectBox.
|
||||
*/
|
||||
fun saveFavorite(place: Place) {
|
||||
place.category = Constants.FAVORITES
|
||||
savePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a place to recent destinations in ObjectBox.
|
||||
* Skips fuel stations, charging stations, and pharmacies.
|
||||
*/
|
||||
fun saveRecent(place: Place) {
|
||||
if (place.category == Constants.FUEL_STATION
|
||||
|| place.category == Constants.CHARGING_STATION
|
||||
@@ -403,6 +490,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
savePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a place to ObjectBox, removing existing duplicates first.
|
||||
* Updates the timestamp to current time.
|
||||
*/
|
||||
private fun savePlace(place: Place) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -426,16 +517,25 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a place from favorites in ObjectBox.
|
||||
*/
|
||||
fun deleteFavorite(place: Place) {
|
||||
place.category = Constants.FAVORITES
|
||||
deletePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a place from recent destinations in ObjectBox.
|
||||
*/
|
||||
fun deleteRecent(place: Place) {
|
||||
place.category = Constants.RECENT
|
||||
deletePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a place from ObjectBox matching name and category.
|
||||
*/
|
||||
fun deletePlace(place: Place) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -456,6 +556,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves search filter settings from preferences.
|
||||
* @return SearchFilter with avoid motorway/tollway flags
|
||||
*/
|
||||
fun getSearchFilter(context: Context): SearchFilter {
|
||||
val repository = getSettingsRepository(context)
|
||||
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
||||
@@ -463,7 +567,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return SearchFilter(avoidMotorway, avoidTollway)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads recent places with calculated distances for Compose state.
|
||||
* @return SnapshotStateList of recent places with distances
|
||||
*/
|
||||
fun loadPlaces2(
|
||||
context: Context,
|
||||
location: Location,
|
||||
@@ -495,6 +602,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return results.toMutableStateList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads recent places as Compose SnapshotStateList.
|
||||
* @return SnapshotStateList of recent places
|
||||
*/
|
||||
fun loadRecentPlace(): SnapshotStateList<Place?> {
|
||||
val results = listOf<Place>()
|
||||
try {
|
||||
@@ -511,5 +622,4 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
return results.toMutableStateList()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ class RouteCalculator(var routeModel: RouteModel) {
|
||||
routeModel.navState.route.currentStepIndex = step.index
|
||||
step.waypointIndex = wayIndex
|
||||
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||
routeModel.navState = routeModel.navState.copy(
|
||||
routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
||||
)
|
||||
//routeModel.navState = routeModel.navState.copy(
|
||||
// routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
||||
// )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.kouros.navigation.model
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_NATIVE
|
||||
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_PROJECTION
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||
import com.kouros.navigation.data.Place
|
||||
@@ -33,7 +35,8 @@ open class RouteModel {
|
||||
val currentLocation: Location = location(0.0, 0.0),
|
||||
val routeBearing: Float = 0F,
|
||||
val currentRouteIndex: Int = 0,
|
||||
val destination: Place = Place()
|
||||
val destination: Place = Place(),
|
||||
val carConnection: Int = 0,
|
||||
)
|
||||
|
||||
var navState = NavigationState()
|
||||
@@ -81,9 +84,8 @@ open class RouteModel {
|
||||
fun updateLocation(context: Context, curLocation: Location, viewModel: NavigationViewModel) {
|
||||
navState = navState.copy(currentLocation = curLocation)
|
||||
routeCalculator.findStep(curLocation)
|
||||
val repository = getSettingsRepository(context)
|
||||
val carLocation = runBlocking { repository.carLocationFlow.first() }
|
||||
if (carLocation) {
|
||||
if (navState.carConnection == CONNECTION_TYPE_PROJECTION
|
||||
|| navState.carConnection == CONNECTION_TYPE_NATIVE) {
|
||||
routeCalculator.updateSpeedLimit(curLocation, viewModel)
|
||||
}
|
||||
navState = navState.copy(lastLocation = navState.currentLocation)
|
||||
|
||||
@@ -27,6 +27,9 @@ object GeoUtils {
|
||||
val point = pointFeature.geometry() as Point
|
||||
newLocation.latitude = point.latitude()
|
||||
newLocation.longitude = point.longitude()
|
||||
if (location.hasBearing()) {
|
||||
newLocation.bearing = location.bearing
|
||||
}
|
||||
}
|
||||
return newLocation
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ fun calculateZoom(speed: Double?): Double {
|
||||
}
|
||||
|
||||
fun previewZoom(previewDistance: Double): Double {
|
||||
when (previewDistance) {
|
||||
when (previewDistance / 1000) {
|
||||
in 0.0..10.0 -> return 13.5
|
||||
in 10.0..20.0 -> return 11.5
|
||||
in 20.0..30.0 -> return 10.5
|
||||
|
||||
10
common/data/src/main/res/drawable/local_gas_station_24.xml
Normal file
10
common/data/src/main/res/drawable/local_gas_station_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.77,7.23l0.01,-0.01 -3.72,-3.72L15,4.56l2.11,2.11c-0.94,0.36 -1.61,1.26 -1.61,2.33 0,1.38 1.12,2.5 2.5,2.5 0.36,0 0.69,-0.08 1,-0.21v7.21c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L17,14c0,-1.1 -0.9,-2 -2,-2h-1L14,5c0,-1.1 -0.9,-2 -2,-2L6,3c-1.1,0 -2,0.9 -2,2v16h10v-7.5h1.5v5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5L20.5,9c0,-0.69 -0.28,-1.32 -0.73,-1.77zM12,10L6,10L6,5h6v5zM18,10c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,840L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L600,480Q633,480 656.5,503.5Q680,527 680,560L680,740Q680,757 691.5,768.5Q703,780 720,780Q737,780 748.5,768.5Q760,757 760,740L760,452Q751,457 741,458.5Q731,460 720,460Q678,460 649,431Q620,402 620,360Q620,328 637.5,302.5Q655,277 684,266L600,182L642,140L790,284Q805,299 812.5,319Q820,339 820,360L820,740Q820,782 791,811Q762,840 720,840Q678,840 649,811Q620,782 620,740L620,540Q620,540 620,540Q620,540 620,540L560,540L560,840L160,840ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM720,400Q737,400 748.5,388.5Q760,377 760,360Q760,343 748.5,331.5Q737,320 720,320Q703,320 691.5,331.5Q680,343 680,360Q680,377 691.5,388.5Q703,400 720,400ZM240,760L480,760L480,480L240,480L240,760ZM480,760L240,760L240,760L480,760Z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.63,468 649.31,482.69Q664,497.37 664,518L664,737Q664,758.68 679.5,773.34Q695,788 717,788Q739,788 754.5,773.34Q770,758.68 770,737L770,442Q759,448 747,451Q735,454 723,454Q683.52,454 656.26,426.74Q629,399.48 629,360Q629,328.39 647,303.19Q665,278 695,270L600,175L636,140L789,293Q803,307 811.5,323.5Q820,340 820,360L820,737Q820,780.26 790.18,810.13Q760.37,840 717.18,840Q674,840 644,810.13Q614,780.26 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM723,404Q741,404 754,391Q767,378 767,360Q767,342 754,329Q741,316 723,316Q705,316 692,329Q679,342 679,360Q679,378 692,391Q705,404 723,404ZM220,780L489,780L489,468L220,468L220,780ZM489,780L220,780L220,780L489,780Z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M120,840L120,760L200,520L120,280L120,200L628,200L686,40L780,74L734,200L840,200L840,280L760,520L840,760L840,840L120,840ZM440,680L520,680L520,560L640,560L640,480L520,480L520,360L440,360L440,480L320,480L320,560L440,560L440,680ZM204,760L756,760L676,520L756,280L204,280L284,520L204,760ZM480,520L480,520L480,520L480,520L480,520L480,520Z"/>
|
||||
</vector>
|
||||
@@ -50,5 +50,7 @@
|
||||
<string name="use_car_location">Auto GPS verwenden</string>
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
<string name="options">Optionen</string>
|
||||
<string name="tomtom_api_key">TomTom ApiKey</string>
|
||||
<string name="use_car_settings">Verwende Auto Einstellungen</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -37,4 +37,5 @@
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="tomtom_api_key">TomTom ApiKey</string>
|
||||
<string name="use_car_settings">Use car settings</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user