Traffic transfer line to right,
Fuel prices from tankerkönig
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 = 104
|
versionCode = 107
|
||||||
versionName = "0.3.0.104"
|
versionName = "0.3.0.107"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
|
|
||||||
var lastRouteDate: LocalDateTime = LocalDateTime.now()
|
var lastRouteDate: LocalDateTime = LocalDateTime.now()
|
||||||
|
|
||||||
var lastAlert : Long = 0
|
|
||||||
var navigationManagerStarted = false
|
var navigationManagerStarted = false
|
||||||
|
|
||||||
var updateLocationIndex = 0
|
var updateLocationIndex = 0
|
||||||
@@ -516,7 +515,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
navigationScreen.updateTrip(
|
navigationScreen.updateTrip(
|
||||||
isNavigating = routeModel.isNavigating(),
|
isNavigating = routeModel.isNavigating(),
|
||||||
isRerouting = false,
|
isRerouting = false,
|
||||||
hasArrived = routeModel.isArrival(),
|
hasArrived = routeModel.isManeuverArrival(),
|
||||||
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext),
|
destinationTravelEstimate = routeModel.getTravelEstimateTrip(carContext),
|
||||||
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext),
|
stepTravelEstimate = routeModel.getTravelEstimateStep(carContext),
|
||||||
destinations = mutableListOf(routeModel.getDestination()),
|
destinations = mutableListOf(routeModel.getDestination()),
|
||||||
@@ -540,7 +539,7 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
* Checks for arrival
|
* Checks for arrival
|
||||||
*/
|
*/
|
||||||
fun checkArrival() {
|
fun checkArrival() {
|
||||||
if (routeModel.isArrival()
|
if (routeModel.isManeuverArrival()
|
||||||
&& routeModel.routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
&& routeModel.routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||||
) {
|
) {
|
||||||
stopNavigation()
|
stopNavigation()
|
||||||
@@ -692,8 +691,10 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
cameras.forEach {
|
cameras.forEach {
|
||||||
coordinates.add(listOf(it.lon, it.lat))
|
coordinates.add(listOf(it.lon, it.lat))
|
||||||
}
|
}
|
||||||
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
synchronized(this) {
|
||||||
surfaceRenderer.speedCameraData.value = speedData
|
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
||||||
|
surfaceRenderer.speedCameraData.value = speedData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -762,12 +763,13 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
* Periodically requests speed camera information near the current location.
|
* Periodically requests speed camera information near the current location.
|
||||||
*/
|
*/
|
||||||
private fun updateSpeedCamera(location: Location) {
|
private fun updateSpeedCamera(location: Location) {
|
||||||
if (lastCameraSearch++ % 100 == 0) {
|
if (lastCameraSearch % 100 == 0) {
|
||||||
navigationViewModel.getSpeedCameras(location, 5.0)
|
navigationViewModel.getSpeedCameras(location, 5.0)
|
||||||
}
|
}
|
||||||
if (speedCameras.isNotEmpty()) {
|
if (speedCameras.isNotEmpty() && lastCameraSearch % 15 == 0) {
|
||||||
updateDistance(location)
|
updateDistance(location)
|
||||||
}
|
}
|
||||||
|
lastCameraSearch++
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -793,10 +795,10 @@ class NavigationSession : CarSession(), NavigationListener, NavigationObserverCa
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
0F
|
0F
|
||||||
}
|
}
|
||||||
if (camera.distance < 80 && (System.currentTimeMillis() - lastAlert > 10000)) {
|
if (camera.distance < 80) {
|
||||||
if ((bearingSpeedCamera - bearingRoute).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()
|
camera.distance = 1000.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,8 +257,7 @@ class SurfaceRenderer(
|
|||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
bearing = bearing,
|
bearing = bearing,
|
||||||
zoom = zoom,
|
zoom = zoom,
|
||||||
target = pos,
|
target = pos
|
||||||
tilt = tilt
|
|
||||||
)
|
)
|
||||||
navigationSession.invalidateNavigationScreen()
|
navigationSession.invalidateNavigationScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,13 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
import com.kouros.navigation.data.NavigationColorDark
|
import com.kouros.navigation.data.NavigationColorDark
|
||||||
import com.kouros.navigation.data.NavigationColorLight
|
import com.kouros.navigation.data.NavigationColorLight
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
import com.kouros.navigation.data.SpeedColor
|
import com.kouros.navigation.data.SpeedColor
|
||||||
import com.kouros.navigation.data.ViewStyle
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.utils.GeoUtils.createEndCollection
|
import com.kouros.navigation.utils.GeoUtils.createEndCollection
|
||||||
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
|
|
||||||
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||||
import com.kouros.navigation.utils.isMetricSystem
|
import com.kouros.navigation.utils.isMetricSystem
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
@@ -51,7 +50,6 @@ import org.maplibre.compose.expressions.dsl.interpolate
|
|||||||
import org.maplibre.compose.expressions.dsl.zoom
|
import org.maplibre.compose.expressions.dsl.zoom
|
||||||
import org.maplibre.compose.expressions.value.ColorValue
|
import org.maplibre.compose.expressions.value.ColorValue
|
||||||
import org.maplibre.compose.expressions.value.DpValue
|
import org.maplibre.compose.expressions.value.DpValue
|
||||||
import org.maplibre.compose.expressions.value.ExpressionValue
|
|
||||||
import org.maplibre.compose.expressions.value.ImageValue
|
import org.maplibre.compose.expressions.value.ImageValue
|
||||||
import org.maplibre.compose.layers.Anchor
|
import org.maplibre.compose.layers.Anchor
|
||||||
import org.maplibre.compose.layers.FillLayer
|
import org.maplibre.compose.layers.FillLayer
|
||||||
@@ -124,14 +122,13 @@ fun MapLibre(
|
|||||||
Puck(cameraState, lastLocation)
|
Puck(cameraState, lastLocation)
|
||||||
AmenityLayer(route)
|
AmenityLayer(route)
|
||||||
} else {
|
} else {
|
||||||
RouteLayer(route)
|
|
||||||
TrafficLayer(traffic!!)
|
TrafficLayer(traffic!!)
|
||||||
|
RouteLayer(route)
|
||||||
StartEndLayer(route)
|
StartEndLayer(route)
|
||||||
//RouteLayerPoint(route )
|
//RouteLayerPoint(route )
|
||||||
}
|
}
|
||||||
SpeedCameraLayer(speedCameras)
|
SpeedCameraLayer(speedCameras)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,17 +180,11 @@ fun RouteLayer(routeData: String?) {
|
|||||||
fun TrafficLayer(trafficData: Map<String, String>) {
|
fun TrafficLayer(trafficData: Map<String, String>) {
|
||||||
trafficData.forEach {
|
trafficData.forEach {
|
||||||
val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value))
|
val traffic = rememberGeoJsonSource(GeoJsonData.JsonString(it.value))
|
||||||
LineLayer(
|
|
||||||
id = "traffic-${it.key}-casing",
|
|
||||||
source = traffic,
|
|
||||||
color = const(Color.White),
|
|
||||||
width = routeLineWidth(base = 2.dp, isCasing = true),
|
|
||||||
)
|
|
||||||
LineLayer(
|
LineLayer(
|
||||||
id = "traffic-${it.key}",
|
id = "traffic-${it.key}",
|
||||||
source = traffic,
|
source = traffic,
|
||||||
color = trafficColor(it.key),
|
color = trafficColor(it.key),
|
||||||
width = routeLineWidth(base = 2.dp, isCasing = false),
|
width = routeLineWidth(base = 1.dp, isCasing = false),
|
||||||
)
|
)
|
||||||
// TrafficIcons(it)
|
// TrafficIcons(it)
|
||||||
}
|
}
|
||||||
@@ -303,7 +294,7 @@ fun trafficColor(key: String): Expression<ColorValue> {
|
|||||||
"stationary" -> return const(Color(0xFF910A0A))
|
"stationary" -> return const(Color(0xFF910A0A))
|
||||||
"heavy" -> return const(Color(0xFF6B0404))
|
"heavy" -> return const(Color(0xFF6B0404))
|
||||||
"roadworks" -> return const(Color(0xFFDAA707))
|
"roadworks" -> return const(Color(0xFFDAA707))
|
||||||
"closed" -> return const(Color(0xFFDA0727))
|
"closed" -> return const(Color(0xFF210C0F))
|
||||||
"lane" -> return const(Color(0xFFEFBAC1))
|
"lane" -> return const(Color(0xFFEFBAC1))
|
||||||
}
|
}
|
||||||
return const(Color.Blue)
|
return const(Color.Blue)
|
||||||
@@ -675,7 +666,7 @@ fun Puck(cameraState: CameraState, location: Location) {
|
|||||||
locationState = location,
|
locationState = location,
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
accuracyThreshold = 10f,
|
accuracyThreshold = 10f,
|
||||||
oldLocationThreshold = 2.seconds,
|
oldLocationThreshold = 1.seconds,
|
||||||
showBearing = false,
|
showBearing = false,
|
||||||
sizes = LocationPuckSizes(dotRadius = 10.dp),
|
sizes = LocationPuckSizes(dotRadius = 10.dp),
|
||||||
colors = LocationPuckColors(
|
colors = LocationPuckColors(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
@@ -19,6 +20,9 @@ import androidx.car.app.navigation.model.MapWithContentTemplate
|
|||||||
import androidx.car.app.versioning.CarAppApiLevels
|
import androidx.car.app.versioning.CarAppApiLevels
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.screen.observers.CategoryObserver
|
import com.kouros.navigation.car.screen.observers.CategoryObserver
|
||||||
@@ -27,12 +31,17 @@ import com.kouros.navigation.data.Constants
|
|||||||
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
||||||
import com.kouros.navigation.data.Constants.FUEL_STATION
|
import com.kouros.navigation.data.Constants.FUEL_STATION
|
||||||
import com.kouros.navigation.data.Constants.PHARMACY
|
import com.kouros.navigation.data.Constants.PHARMACY
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||||
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import com.kouros.navigation.utils.round
|
import com.kouros.navigation.utils.round
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class CategoryScreen(
|
class CategoryScreen(
|
||||||
@@ -43,9 +52,15 @@ class CategoryScreen(
|
|||||||
|
|
||||||
) : Screen(carContext), CategoryObserverCallback {
|
) : Screen(carContext), CategoryObserverCallback {
|
||||||
|
|
||||||
|
|
||||||
|
val repository = getSettingsRepository(carContext)
|
||||||
|
|
||||||
|
val settingsViewModel = getSettingsViewModel(carContext)
|
||||||
|
|
||||||
val maxListItems: Int = 30
|
val maxListItems: Int = 30
|
||||||
|
|
||||||
var elements: List<Elements> = emptyList()
|
var elements: List<Elements> = emptyList()
|
||||||
|
|
||||||
private val categoryObserver = CategoryObserver(this)
|
private val categoryObserver = CategoryObserver(this)
|
||||||
|
|
||||||
private var loading = true
|
private var loading = true
|
||||||
@@ -56,8 +71,13 @@ class CategoryScreen(
|
|||||||
navigationViewModel.elements.value = emptyList()
|
navigationViewModel.elements.value = emptyList()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
repository.lastFuelPricesFlow.asLiveData().observe(this, Observer {
|
||||||
|
navigationViewModel.getAmenities(carContext, category, surfaceRenderer.lastLocation, it)
|
||||||
|
})
|
||||||
|
|
||||||
navigationViewModel.elements.observe(this, categoryObserver)
|
navigationViewModel.elements.observe(this, categoryObserver)
|
||||||
navigationViewModel.getAmenities(category, surfaceRenderer.lastLocation)
|
//navigationViewModel.getAmenities(carContext, category, surfaceRenderer.lastLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
@@ -134,17 +154,17 @@ class CategoryScreen(
|
|||||||
}
|
}
|
||||||
.setTitle(name)
|
.setTitle(name)
|
||||||
.setImage(carIcon(carContext, category, index))
|
.setImage(carIcon(carContext, category, index))
|
||||||
|
if (category == CHARGING_STATION) {
|
||||||
|
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
|
||||||
|
} else {
|
||||||
|
row.addText(carText(" ${it.e5} / ${it.e10} € ${it.tags.openingHours}"))
|
||||||
|
}
|
||||||
if (it.distance < 1000) {
|
if (it.distance < 1000) {
|
||||||
row.addText("${(it.distance).toInt()} m")
|
row.addText("${(it.distance).toInt()} m")
|
||||||
} else {
|
} else {
|
||||||
row.addText("${(it.distance / 1000).round(1)} km")
|
row.addText("${(it.distance / 1000).round(1)} km")
|
||||||
}
|
}
|
||||||
if (category == CHARGING_STATION) {
|
|
||||||
if (it.tags.socketType2 != null)
|
|
||||||
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
|
|
||||||
} else {
|
|
||||||
row.addText(carText("${it.tags.openingHours}"))
|
|
||||||
}
|
|
||||||
row.addAction(
|
row.addAction(
|
||||||
createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT, {
|
createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT, {
|
||||||
navigationViewModel.loadRoute(
|
navigationViewModel.loadRoute(
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ open class NavigationScreen(
|
|||||||
carContext = carContext, R.drawable.ic_recenter_24,
|
carContext = carContext, R.drawable.ic_recenter_24,
|
||||||
0,
|
0,
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.setStandardView()
|
||||||
invalidate()
|
invalidate()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.kouros.navigation.car.screen
|
|||||||
|
|
||||||
import androidx.car.app.testing.ScreenController
|
import androidx.car.app.testing.ScreenController
|
||||||
import androidx.car.app.testing.TestCarContext
|
import androidx.car.app.testing.TestCarContext
|
||||||
import androidx.car.app.navigation.model.NavigationTemplate
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
@@ -63,7 +62,6 @@ class NavigationScreenTest {
|
|||||||
navigationScreen = NavigationScreen(
|
navigationScreen = NavigationScreen(
|
||||||
testCarContext,
|
testCarContext,
|
||||||
mockSurfaceRenderer,
|
mockSurfaceRenderer,
|
||||||
mockRouteModel,
|
|
||||||
mockListener,
|
mockListener,
|
||||||
mockViewModel
|
mockViewModel
|
||||||
)
|
)
|
||||||
@@ -120,12 +118,12 @@ class NavigationScreenTest {
|
|||||||
// Arrange
|
// Arrange
|
||||||
navigationScreen.navigationType = NavigationType.NAVIGATION
|
navigationScreen.navigationType = NavigationType.NAVIGATION
|
||||||
|
|
||||||
`when`(mockRouteModel.isArrival()).thenReturn(true)
|
`when`(mockRouteModel.isManeuverArrival()).thenReturn(true)
|
||||||
`when`(mockRouteModel.routeCalculator).thenReturn(mockRouteCalculator)
|
`when`(mockRouteModel.routeCalculator).thenReturn(mockRouteCalculator)
|
||||||
`when`(mockRouteCalculator.leftStepDistance()).thenReturn(19.0)
|
`when`(mockRouteCalculator.leftStepDistance()).thenReturn(19.0)
|
||||||
`when`(mockRouteModel.navState).thenReturn(NavigationState())
|
`when`(mockRouteModel.navState).thenReturn(NavigationState())
|
||||||
// Act
|
// Act
|
||||||
navigationScreen.checkArrival()
|
//navigationScreen.checkArrival()
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertThat(navigationScreen.navigationType).isEqualTo(NavigationType.ARRIVAL)
|
assertThat(navigationScreen.navigationType).isEqualTo(NavigationType.ARRIVAL)
|
||||||
|
|||||||
@@ -136,9 +136,9 @@ object Constants {
|
|||||||
|
|
||||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||||
|
|
||||||
const val MAXIMAL_ROUTE_DEVIATION = 80.0
|
const val MAXIMAL_ROUTE_DEVIATION = 100.0
|
||||||
|
|
||||||
const val DESTINATION_ARRIVAL_DISTANCE = 20.0
|
const val DESTINATION_ARRIVAL_DISTANCE = 10.0
|
||||||
|
|
||||||
const val NEAREST_LOCATION_DISTANCE = 10F
|
const val NEAREST_LOCATION_DISTANCE = 10F
|
||||||
|
|
||||||
@@ -159,6 +159,8 @@ object Constants {
|
|||||||
const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED"
|
const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED"
|
||||||
|
|
||||||
const val TILT = 60.0
|
const val TILT = 60.0
|
||||||
|
|
||||||
|
const val TANKER_KOENIG_DELAY = 300000
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.longPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import com.kouros.navigation.data.EngineType
|
import com.kouros.navigation.data.EngineType
|
||||||
@@ -59,6 +60,10 @@ class DataStoreManager(private val context: Context) {
|
|||||||
|
|
||||||
val ALTERNATIVE_ROUTES = booleanPreferencesKey("AlternativeRoutes")
|
val ALTERNATIVE_ROUTES = booleanPreferencesKey("AlternativeRoutes")
|
||||||
|
|
||||||
|
val LAST_FUEL_PRICES = longPreferencesKey("LastFuelPrices")
|
||||||
|
|
||||||
|
val FUEL_PRICES = stringPreferencesKey("FuelPrices")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read values
|
// Read values
|
||||||
@@ -151,6 +156,18 @@ class DataStoreManager(private val context: Context) {
|
|||||||
preferences[ALTERNATIVE_ROUTES] == true
|
preferences[ALTERNATIVE_ROUTES] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lastFuelPricesFlow: Flow<Long> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[LAST_FUEL_PRICES]
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val fuelPricesFlow: Flow<String> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[FUEL_PRICES]
|
||||||
|
?: ""
|
||||||
|
}
|
||||||
|
|
||||||
// Save values
|
// Save values
|
||||||
suspend fun setShow3D(enabled: Boolean) {
|
suspend fun setShow3D(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
@@ -248,4 +265,16 @@ class DataStoreManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setLastFuelPrices(lastFuelPrices: Long) {
|
||||||
|
context.dataStore.edit { prefs ->
|
||||||
|
prefs[LAST_FUEL_PRICES] = lastFuelPrices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setFuelPrices(fuelPrices: String) {
|
||||||
|
context.dataStore.edit { prefs ->
|
||||||
|
prefs[FUEL_PRICES] = fuelPrices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.kouros.data.BuildConfig
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.SearchFilter
|
import com.kouros.navigation.data.SearchFilter
|
||||||
import com.kouros.navigation.data.overpass.Amenity
|
import com.kouros.navigation.data.overpass.Amenity
|
||||||
@@ -13,6 +14,7 @@ import java.net.HttpURLConnection
|
|||||||
import java.net.PasswordAuthentication
|
import java.net.PasswordAuthentication
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
|
||||||
const val tankerKoenigUrl = "https://creativecommons.tankerkoenig.de/json/list.php?"
|
const val tankerKoenigUrl = "https://creativecommons.tankerkoenig.de/json/list.php?"
|
||||||
|
|
||||||
private val gson = GsonBuilder().serializeNulls().create()
|
private val gson = GsonBuilder().serializeNulls().create()
|
||||||
@@ -20,11 +22,18 @@ private val gson = GsonBuilder().serializeNulls().create()
|
|||||||
const val sort = "&sort=dist&type=all"
|
const val sort = "&sort=dist&type=all"
|
||||||
|
|
||||||
const val apiKey = "&apikey=fc9900c9-8f02-4b28-990d-dc6067228c59"
|
const val apiKey = "&apikey=fc9900c9-8f02-4b28-990d-dc6067228c59"
|
||||||
|
|
||||||
|
val useLocal = BuildConfig.DEBUG
|
||||||
|
|
||||||
class FuelPrices : NavigationRepository() {
|
class FuelPrices : NavigationRepository() {
|
||||||
|
|
||||||
fun getFuelPrices(location: Location, radius: Int) : List<Station> {
|
fun getFuelPrices(location: Location, radius: Int) : List<Station> {
|
||||||
|
|
||||||
val url = "${tankerKoenigUrl}lat=${location.latitude}&lng=${location.longitude}&rad=${radius}$sort$apiKey"
|
val url = if (useLocal) {
|
||||||
|
"http://192.168.1.37/fuel.json"
|
||||||
|
} else {
|
||||||
|
"${tankerKoenigUrl}lat=${location.latitude}&lng=${location.longitude}&rad=${radius}$sort$apiKey"
|
||||||
|
}
|
||||||
|
|
||||||
val prices = fetchUrl(
|
val prices = fetchUrl(
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package com.kouros.navigation.data.fuel
|
package com.kouros.navigation.data.fuel
|
||||||
|
|
||||||
|
import com.kouros.navigation.data.Place
|
||||||
|
|
||||||
|
data class Stations(
|
||||||
|
val stations: List<Station>,
|
||||||
|
)
|
||||||
data class Station(
|
data class Station(
|
||||||
val brand: String,
|
val brand: String,
|
||||||
val diesel: Double,
|
val diesel: Double,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ data class Elements(
|
|||||||
val lon: Double = 0.0,
|
val lon: Double = 0.0,
|
||||||
val tags: Tags,
|
val tags: Tags,
|
||||||
val type: String = "",
|
val type: String = "",
|
||||||
var distance : Double = 0.0
|
var distance : Double = 0.0,
|
||||||
|
var e5 : Double = 0.0,
|
||||||
|
var e10: Double = 0.0,
|
||||||
|
var diesel: Double = 0.0
|
||||||
|
|
||||||
)
|
)
|
||||||
@@ -14,22 +14,29 @@ import com.kouros.navigation.data.Constants
|
|||||||
import com.kouros.navigation.data.Constants.SPEED_BEARING_DEVIATION
|
import com.kouros.navigation.data.Constants.SPEED_BEARING_DEVIATION
|
||||||
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
|
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
|
import com.kouros.navigation.data.Constants.TANKER_KOENIG_DELAY
|
||||||
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.data.Places
|
import com.kouros.navigation.data.Places
|
||||||
import com.kouros.navigation.data.SearchFilter
|
import com.kouros.navigation.data.SearchFilter
|
||||||
|
import com.kouros.navigation.data.fuel.FuelPrice
|
||||||
import com.kouros.navigation.data.fuel.FuelPrices
|
import com.kouros.navigation.data.fuel.FuelPrices
|
||||||
|
import com.kouros.navigation.data.fuel.Station
|
||||||
|
import com.kouros.navigation.data.fuel.Stations
|
||||||
import com.kouros.navigation.data.nominatim.Search
|
import com.kouros.navigation.data.nominatim.Search
|
||||||
import com.kouros.navigation.data.nominatim.SearchResult
|
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.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
|
import com.kouros.navigation.utils.GeoUtils.transferCoordinates
|
||||||
import com.kouros.navigation.utils.bearingPositive
|
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.getSettingsViewModel
|
||||||
import com.kouros.navigation.utils.isNumeric
|
import com.kouros.navigation.utils.isNumeric
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
|
import com.kouros.navigation.utils.settingsViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -273,26 +280,25 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
val incidents = mutableMapOf<String, String>()
|
val incidents = mutableMapOf<String, String>()
|
||||||
val queuing = featureCollection.features()!!
|
val queuing = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Queuing traffic") }
|
.filter { it.properties()!!.get("events").toString().contains("Queuing traffic") }
|
||||||
incidents["queuing"] = FeatureCollection.fromFeatures(queuing).toJson()
|
incidents["queuing"] = transferCoordinates(queuing)
|
||||||
val stationary = featureCollection.features()!!
|
val stationary = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Stationary traffic") }
|
.filter { it.properties()!!.get("events").toString().contains("Stationary traffic") }
|
||||||
incidents["stationary"] = FeatureCollection.fromFeatures(stationary).toJson()
|
incidents["stationary"] = transferCoordinates(stationary)
|
||||||
val slow = featureCollection.features()!!
|
val slow = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Slow traffic") }
|
.filter { it.properties()!!.get("events").toString().contains("Slow traffic") }
|
||||||
incidents["slow"] = FeatureCollection.fromFeatures(slow).toJson()
|
incidents["slow"] = transferCoordinates(slow)
|
||||||
val heavy = featureCollection.features()!!
|
val heavy = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Heavy traffic") }
|
.filter { it.properties()!!.get("events").toString().contains("Heavy traffic") }
|
||||||
incidents["heavy"] = FeatureCollection.fromFeatures(heavy).toJson()
|
incidents["heavy"] = transferCoordinates(heavy)
|
||||||
val roadworks = featureCollection.features()!!
|
val roadworks = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Roadworks") }
|
.filter { it.properties()!!.get("events").toString().contains("Roadworks") }
|
||||||
incidents["roadworks"] = FeatureCollection.fromFeatures(roadworks).toJson()
|
incidents["roadworks"] = transferCoordinates(roadworks)
|
||||||
val laneClosed = featureCollection.features()!!
|
val laneClosed = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Lane closed") }
|
.filter { it.properties()!!.get("events").toString().contains("Lane closed") }
|
||||||
incidents["lane"] = FeatureCollection.fromFeatures(laneClosed).toJson()
|
incidents["lane"] = transferCoordinates(laneClosed)
|
||||||
val closed = featureCollection.features()!!
|
val closed = featureCollection.features()!!
|
||||||
.filter { it.properties()!!.get("events").toString().contains("Closed") }
|
.filter { it.properties()!!.get("events").toString().contains("Closed") }
|
||||||
incidents["closed"] = FeatureCollection.fromFeatures(closed).toJson()
|
incidents["closed"] = transferCoordinates(closed)
|
||||||
|
|
||||||
|
|
||||||
return incidents
|
return incidents
|
||||||
}
|
}
|
||||||
@@ -391,15 +397,30 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
* Queries Overpass API for nearby amenities of a specific category.
|
* Queries Overpass API for nearby amenities of a specific category.
|
||||||
* Posts sorted results to elements LiveData.
|
* Posts sorted results to elements LiveData.
|
||||||
*/
|
*/
|
||||||
fun getAmenities(category: String, location: Location) {
|
fun getAmenities(carContext: Context, category: String, location: Location, lastFuelUpdate: Long = 0) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val repository = getSettingsRepository(carContext)
|
||||||
|
var fuelPrices: List<Station>
|
||||||
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
|
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (category == Constants.FUEL_STATION && ((now - lastFuelUpdate) > TANKER_KOENIG_DELAY)) {
|
||||||
|
fuelPrices = FuelPrices().getFuelPrices(location, 5)
|
||||||
|
repository.setLastFuelPrices(System.currentTimeMillis())
|
||||||
|
repository.setFuelPrices(gson.toJson(Stations(fuelPrices)))
|
||||||
|
} else {
|
||||||
|
val fuels = repository.fuelPricesFlow.first()
|
||||||
|
fuelPrices =
|
||||||
|
gson.fromJson(fuels, Stations::class.java).stations
|
||||||
|
}
|
||||||
val distAmenities = mutableListOf<Elements>()
|
val distAmenities = mutableListOf<Elements>()
|
||||||
amenities.forEach {
|
amenities.forEach {
|
||||||
val plLocation =
|
val plLocation =
|
||||||
location(longitude = it.lon, latitude = it.lat)
|
location(longitude = it.lon, latitude = it.lat)
|
||||||
val distance = plLocation.distanceTo(location)
|
val distance = plLocation.distanceTo(location)
|
||||||
it.distance = distance.toDouble()
|
it.distance = distance.toDouble()
|
||||||
|
if (category == Constants.FUEL_STATION) {
|
||||||
|
addFuelPrice(it, fuelPrices, location)
|
||||||
|
}
|
||||||
distAmenities.add(it)
|
distAmenities.add(it)
|
||||||
}
|
}
|
||||||
val sortedList = distAmenities.sortedWith(compareBy { it.distance })
|
val sortedList = distAmenities.sortedWith(compareBy { it.distance })
|
||||||
@@ -407,6 +428,21 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addFuelPrice(fuel: Elements, fuelPrices: List<Station>, location: Location) {
|
||||||
|
fuelPrices.forEach {
|
||||||
|
if (fuel.tags.name.equals(it.brand, ignoreCase = true)) {
|
||||||
|
val plLocation =
|
||||||
|
location(longitude = it.lng, latitude = it.lat)
|
||||||
|
val distance = plLocation.distanceTo(location)
|
||||||
|
if ((distance - fuel.distance).absoluteValue < 30) {
|
||||||
|
fuel.e10 = it.e10
|
||||||
|
fuel.e5 = it.e5
|
||||||
|
fuel.diesel = it.diesel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries Overpass API for speed cameras within a radius.
|
* Queries Overpass API for speed cameras within a radius.
|
||||||
* Posts sorted results to speedCameras LiveData.
|
* Posts sorted results to speedCameras LiveData.
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
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
|
||||||
@@ -170,7 +168,7 @@ open class RouteModel {
|
|||||||
/**
|
/**
|
||||||
* Checks for arrival
|
* Checks for arrival
|
||||||
*/
|
*/
|
||||||
fun isArrival(): Boolean {
|
fun isManeuverArrival(): Boolean {
|
||||||
return navState.maneuverType == Maneuver.TYPE_DESTINATION
|
return navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||||
|
|||||||
@@ -105,6 +105,18 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val lastFuelPrices = repository.lastFuelPricesFlow.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(5_000),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
val fuelPrices = repository.fuelPricesFlow.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(5_000),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
fun onShow3DChanged(enabled: Boolean) {
|
fun onShow3DChanged(enabled: Boolean) {
|
||||||
viewModelScope.launch { repository.setShow3D(enabled) }
|
viewModelScope.launch { repository.setShow3D(enabled) }
|
||||||
}
|
}
|
||||||
@@ -165,4 +177,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
|||||||
fun onAlternativeRoutes(enabled: Boolean) {
|
fun onAlternativeRoutes(enabled: Boolean) {
|
||||||
viewModelScope.launch { repository.setAlternativeRoutes(enabled) }
|
viewModelScope.launch { repository.setAlternativeRoutes(enabled) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onLastFuelPricesChanged(lastFuelPrices: Long) {
|
||||||
|
viewModelScope.launch { repository.setLastFuelPrices(lastFuelPrices) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFuelPricesChanged(fuelPrices: String) {
|
||||||
|
viewModelScope.launch { repository.setFuelPrices(fuelPrices) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.repository
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
import com.kouros.navigation.data.datastore.DataStoreManager
|
||||||
|
import com.kouros.navigation.data.fuel.FuelPrices
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class SettingsRepository(
|
class SettingsRepository(
|
||||||
@@ -54,6 +55,13 @@ class SettingsRepository(
|
|||||||
val alternativeRoutesFlow: Flow<Boolean> =
|
val alternativeRoutesFlow: Flow<Boolean> =
|
||||||
dataStoreManager.alternativeRoutesFlow
|
dataStoreManager.alternativeRoutesFlow
|
||||||
|
|
||||||
|
val lastFuelPricesFlow: Flow<Long> =
|
||||||
|
dataStoreManager.lastFuelPricesFlow
|
||||||
|
|
||||||
|
val fuelPricesFlow: Flow<String> =
|
||||||
|
dataStoreManager.fuelPricesFlow
|
||||||
|
|
||||||
|
|
||||||
suspend fun setShow3D(enabled: Boolean) {
|
suspend fun setShow3D(enabled: Boolean) {
|
||||||
dataStoreManager.setShow3D(enabled)
|
dataStoreManager.setShow3D(enabled)
|
||||||
}
|
}
|
||||||
@@ -117,4 +125,12 @@ class SettingsRepository(
|
|||||||
suspend fun setAlternativeRoutes(enabled: Boolean) {
|
suspend fun setAlternativeRoutes(enabled: Boolean) {
|
||||||
dataStoreManager.setAlternativeRoutes(enabled)
|
dataStoreManager.setAlternativeRoutes(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setLastFuelPrices(lastFuelPrices: Long) {
|
||||||
|
dataStoreManager.setLastFuelPrices(lastFuelPrices)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setFuelPrices(fuelPrices: String) {
|
||||||
|
dataStoreManager.setFuelPrices(fuelPrices)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.kouros.navigation.utils
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.util.Log
|
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
|
||||||
@@ -17,8 +16,11 @@ import org.maplibre.turf.TurfMeasurement
|
|||||||
import org.maplibre.turf.TurfMisc
|
import org.maplibre.turf.TurfMisc
|
||||||
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.atan2
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
object GeoUtils {
|
object GeoUtils {
|
||||||
|
|
||||||
@@ -185,4 +187,64 @@ object GeoUtils {
|
|||||||
&& location.longitude <= topRightLon
|
&& location.longitude <= topRightLon
|
||||||
return isInside
|
return isInside
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun transferCoordinates(features: List<org.maplibre.geojson.Feature>): String {
|
||||||
|
return buildFeatureCollection {
|
||||||
|
features.forEach {
|
||||||
|
val movedCoordinates = arrayListOf<org.maplibre.spatialk.geojson.Point>()
|
||||||
|
val coordinates = it.geometry() as LineString
|
||||||
|
val geo = coordinates.coordinates()
|
||||||
|
var bearing = 0.0F
|
||||||
|
for (index in 0..<geo.size) {
|
||||||
|
val current = location(geo[index].longitude(), geo[index].latitude())
|
||||||
|
bearing = if (index < geo.size - 1) {
|
||||||
|
val next =
|
||||||
|
location(geo[index + 1].longitude(), geo[index + 1].latitude())
|
||||||
|
current.bearingPositive(next)
|
||||||
|
} else {
|
||||||
|
bearing
|
||||||
|
}
|
||||||
|
val point = movePoint(
|
||||||
|
current.latitude,
|
||||||
|
current.longitude,
|
||||||
|
bearingDegrees = bearing + 90.0
|
||||||
|
)
|
||||||
|
movedCoordinates.add(point)
|
||||||
|
}
|
||||||
|
val lineString = buildLineString {
|
||||||
|
movedCoordinates.forEach { ft ->
|
||||||
|
add(
|
||||||
|
ft
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFeature {
|
||||||
|
geometry = lineString
|
||||||
|
properties = buildJsonObject { null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.toJson()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun movePoint(
|
||||||
|
latitude: Double,
|
||||||
|
longitude: Double,
|
||||||
|
distanceMeters: Double = 7.0,
|
||||||
|
bearingDegrees: Double = 0.0
|
||||||
|
): org.maplibre.spatialk.geojson.Point {
|
||||||
|
val earthRadius = 6371000.0 // meters
|
||||||
|
val bearingRadians = toRadians(bearingDegrees)
|
||||||
|
val lat1 = toRadians(latitude)
|
||||||
|
val lon1 = toRadians(longitude)
|
||||||
|
|
||||||
|
val lat2 = asin(
|
||||||
|
sin(lat1) * cos(distanceMeters / earthRadius) +
|
||||||
|
cos(lat1) * sin(distanceMeters / earthRadius) * cos(bearingRadians)
|
||||||
|
)
|
||||||
|
val lon2 = lon1 + atan2(
|
||||||
|
sin(bearingRadians) * sin(distanceMeters / earthRadius) * cos(lat1),
|
||||||
|
cos(distanceMeters / earthRadius) - sin(lat1) * sin(lat2)
|
||||||
|
)
|
||||||
|
return org.maplibre.spatialk.geojson.Point(toDegrees(lon2), toDegrees(lat2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user