Speed radar

This commit is contained in:
Dimitris
2025-12-19 15:03:32 +01:00
parent b9030dbc50
commit 9e453dc955
31 changed files with 597 additions and 412 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId = "com.kouros.navigation"
minSdk = 33
targetSdk = 36
versionCode = 10
versionName = "0.1.3.10"
versionCode = 11
versionName = "0.1.3.11"
base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
tools:ignore="MockLocation" />
@@ -18,6 +19,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.Navigation">
<meta-data

View File

@@ -46,6 +46,7 @@ import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.kouros.data.R
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.StepData
@@ -87,8 +88,8 @@ class MainActivity : ComponentActivity() {
if (newRoute.isNotEmpty()) {
routeModel.startNavigation(newRoute)
routeData.value = routeModel.route.routeGeoJson
//mock.setMockLocation(homeLocation.latitude, homeLocation.longitude)
simulate()
//test()
}
}
val cameraPosition = MutableLiveData(
@@ -237,10 +238,6 @@ class MainActivity : ComponentActivity() {
&& lastLocation.latitude != location.position.latitude
&& lastLocation.longitude != location.position.longitude
) {
if (lastLocation.latitude != 0.0 && !overpass) {
//viewModel.getAmenities(Constants.CHARGING_STATION, lastLocation)
//overpass = true
}
val currentLocation = location(location.position.longitude, location.position.latitude)
with(routeModel) {
if (isNavigating()) {
@@ -249,6 +246,13 @@ class MainActivity : ComponentActivity() {
if (route.currentManeuverIndex + 1 <= route.maneuvers.size) {
nextStepData.value = nextStep()
}
if (routeState.maneuverType == 39
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
stopNavigation()
routeState = routeState.copy(arrived = true)
routeData.value = ""
}
}
}
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
@@ -312,4 +316,17 @@ class MainActivity : ComponentActivity() {
}
}
}
fun test() {
for ((index, loc) in routeModel.route.waypoints.withIndex()) {
if (index > 300) {
routeModel.updateLocation(location(loc[0], loc[1]))
routeModel.currentStep()
if (routeModel.route.currentManeuverIndex + 1 <= routeModel.route.maneuvers.size) {
nextStepData.value = routeModel.nextStep()
}
println(routeModel.routeState.maneuverType)
}
}
}
}

View File

@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -32,8 +34,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
}

View File

@@ -180,6 +180,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
routeModel.stopNavigation()
}
companion object {
var uriHost: String = "navigation"

View File

@@ -37,7 +37,7 @@ import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calcTilt
import com.kouros.navigation.utils.calculateTilt
import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.duration
import com.kouros.navigation.utils.location
@@ -225,7 +225,7 @@ class SurfaceRenderer(
} else {
cameraPosition.value!!.zoom + 1.0
}
tilt = calcTilt(newZoom, tilt)
tilt = calculateTilt(newZoom, tilt)
updateCameraPosition(
cameraPosition.value!!.bearing,
newZoom,
@@ -310,7 +310,7 @@ class SurfaceRenderer(
}
fun setCategories(location: Location, route: String) {
viewStyle = ViewStyle.SEARCH_VIEW
viewStyle = ViewStyle.AMENITY_VIEW
routeData.value = route
updateCameraPosition(
0.0,
@@ -319,14 +319,13 @@ class SurfaceRenderer(
)
}
fun setCategoryLocation(location: Location) {
viewStyle = ViewStyle.SEARCH_VIEW
fun setCategoryLocation(location: Location, category: String) {
viewStyle = ViewStyle.AMENITY_VIEW
cameraPosition.postValue(
cameraPosition.value!!.copy(
target = Position(location.longitude, location.latitude)
)
)
}
companion
@@ -337,6 +336,6 @@ class SurfaceRenderer(
enum class ViewStyle {
VIEW, PREVIEW, PAN_VIEW, SEARCH_VIEW
VIEW, PREVIEW, PAN_VIEW, AMENITY_VIEW
}

View File

@@ -33,16 +33,18 @@ import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.expressions.dsl.contains
import org.maplibre.compose.expressions.dsl.exponential
import org.maplibre.compose.expressions.dsl.image
import org.maplibre.compose.expressions.dsl.interpolate
import org.maplibre.compose.expressions.dsl.zoom
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.CircleLayer
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.layers.SymbolLayer
@@ -58,9 +60,15 @@ import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.getBaseSource
import org.maplibre.compose.sources.rememberGeoJsonSource
import org.maplibre.compose.style.BaseStyle
import org.maplibre.geojson.FeatureCollection
import org.maplibre.spatialk.geojson.BoundingBox.Companion.serializer
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.FeatureCollection
import org.maplibre.spatialk.geojson.GeoJson
import org.maplibre.spatialk.geojson.Geometry
import org.maplibre.spatialk.geojson.Position
import org.maplibre.spatialk.geojson.dsl.FeatureBuilder
import org.maplibre.spatialk.geojson.dsl.FeatureCollectionBuilder
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
@Composable
@@ -103,19 +111,22 @@ fun MapLibre(
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
BuildingLayer(tiles)
}
RouteLayer(route, viewStyle)
if (viewStyle == ViewStyle.AMENITY_VIEW) {
AmenityLayer(route)
} else {
RouteLayer(route)
}
}
//Puck(cameraState, lastLocation)
}
}
@Composable
fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
fun RouteLayer(routeData: String?) {
if (routeData != null && routeData.isNotEmpty()) {
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
if (viewStyle == ViewStyle.VIEW) {
LineLayer(
id = "routes-casing$viewStyle",
id = "routes-casing",
source = routes,
color = const(Color.White),
width =
@@ -129,7 +140,7 @@ fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
),
)
LineLayer(
id = "routes$viewStyle",
id = "routes",
source = routes,
color = const(RouteColor),
width =
@@ -142,21 +153,27 @@ fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
20 to const(22.dp),
),
)
} else {
SymbolLayer(
id = "my-symbol-layer",
source = routes,
// Convert a drawable resource to a MapLibre image
// drawAsSdf = true allows us to tint the image programmatically
iconImage = image(painterResource(com.kouros.android.cars.carappservice.R.drawable.ev_station_24px), drawAsSdf = true),
// Now we can apply any color we want!
iconColor = const(Color.Red),
iconSize = const(5.0f)
)
}
}
}
@Composable
fun AmenityLayer(routeData: String?) {
if (routeData != null && routeData.isNotEmpty()) {
val color = if (routeData.contains(Constants.PHARMACY)) {
const(Color.Red)
} else {
const(Color.Green)
}
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
SymbolLayer(
id = "amenity-layer",
source = routes,
iconImage = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true),
iconColor = color,
iconSize = const(3.0f),
)
}
}
@Composable
fun BuildingLayer(tiles: Source) {
Anchor.Replace("building-3d") {

View File

@@ -18,7 +18,12 @@ package com.kouros.navigation.car.navigation
import android.text.SpannableString
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.car.app.AppManager
import androidx.car.app.CarContext
import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.Alert
import androidx.car.app.model.AlertCallback
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
@@ -28,8 +33,7 @@ import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate
import androidx.core.graphics.drawable.IconCompat
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType
import com.kouros.data.R
import com.kouros.navigation.model.RouteModel
import java.util.TimeZone
import java.util.concurrent.TimeUnit
@@ -117,4 +121,43 @@ class RouteCarModel() : RouteModel() {
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
carContext.getCarService<AppManager?>(AppManager::class.java)
.showAlert(createAlert(carContext, distance, maxSpeed))
}
fun createAlert(carContext: CarContext, distance: Double, maxSpeed: String?): Alert {
val title = createCarText(carContext,R.string.speed_camera)
val subtitle = CarText.create(maxSpeed!!)
val icon = CarIcon.ALERT
val dismissAction: Action = createToastAction(
carContext,
R.string.speed_camera, R.string.exit_action_title,
FLAG_DEFAULT
)
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */10000)
.setSubtitle(subtitle)
.setIcon(icon)
.addAction(dismissAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) {
}
override fun onDismiss() {
}
}).build()
}
private fun createToastAction(
carContext: CarContext,
@StringRes titleRes: Int, @StringRes toastStringRes: Int,
flags: Int
): Action {
return Action.Builder()
.setOnClickListener { }
.setTitle(createCarText(carContext,titleRes))
.setFlags(flags)
.build()
}
}

View File

@@ -15,7 +15,9 @@ import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.CHARGING_STATION
import com.kouros.navigation.data.Constants.FUEL_STATION
import com.kouros.navigation.data.Constants.PHARMACY
class CategoriesScreen(
private val carContext: CarContext,
@@ -24,9 +26,9 @@ class CategoriesScreen(
) : Screen(carContext) {
var categories: List<Category> = listOf(
Category(id = Constants.FUEL_STATION, name = carContext.getString(R.string.fuel_station)),
Category(id = Constants.PHARMACY, name = carContext.getString(R.string.pharmacy)),
Category(id = Constants.CHARGING_STATION, name = carContext.getString(R.string.charging_station))
Category(id = FUEL_STATION, name = carContext.getString(R.string.fuel_station)),
Category(id = PHARMACY, name = carContext.getString(R.string.pharmacy)),
Category(id = CHARGING_STATION, name = carContext.getString(R.string.charging_station))
)
override fun onGetTemplate(): Template {
@@ -36,13 +38,7 @@ class CategoriesScreen(
itemListBuilder.addItem(
Row.Builder()
.setTitle(it.name)
.setImage(CarIcon.Builder(
IconCompat.createWithResource(
carContext,
com.kouros.android.cars.carappservice.R.drawable.ev_station_24px
)
)
.build())
.setImage(carIcon(carContext,it.id))
.setOnClickListener {
screenManager
.pushForResult(
@@ -64,7 +60,7 @@ class CategoriesScreen(
)
}
surfaceRenderer.viewStyle = ViewStyle.SEARCH_VIEW
surfaceRenderer.viewStyle = ViewStyle.AMENITY_VIEW
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
@@ -77,3 +73,19 @@ class CategoriesScreen(
.build()
}
}
fun carIcon(context: CarContext, id: String): CarIcon {
val resId = when (id) {
FUEL_STATION -> R.drawable.local_gas_station_48px
PHARMACY -> R.drawable.local_pharmacy_48px
CHARGING_STATION -> R.drawable.ev_station_48px
else -> {}
}
return CarIcon.Builder(
IconCompat.createWithResource(
context,
resId as Int
)
)
.build()
}

View File

@@ -3,12 +3,10 @@ package com.kouros.navigation.car.screen
import android.location.Location
import androidx.annotation.DrawableRes
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action
import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
@@ -17,24 +15,24 @@ import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.versioning.CarAppApiLevels
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.GeoUtils.createPointCollection
import com.kouros.navigation.utils.location
import com.kouros.navigation.utils.round
import kotlin.math.min
class CategoryScreen(
private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer,
location: Location,
category: String,
private val category: String,
) : Screen(carContext) {
val viewModel = ViewModel(NavigationRepository())
@@ -52,7 +50,7 @@ class CategoryScreen(
coordinates.add(listOf(it.lon!!, it.lat!!))
}
if (elements.isNotEmpty()) {
val route = createPointCollection(coordinates)
val route = createPointCollection(coordinates, category)
surfaceRenderer.setCategories(loc, route)
invalidate()
}
@@ -65,12 +63,10 @@ class CategoryScreen(
override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder()
if (carContext.getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
var index = 0
val listLimit = min(
100,
50,
carContext.getCarService(ConstraintManager::class.java)
.getContentLimit(
ConstraintManager.CONTENT_LIMIT_TYPE_LIST
@@ -78,28 +74,14 @@ class CategoryScreen(
)
elements.forEach {
if (index++ < listLimit) {
if (it.tags.operator != null) {
listBuilder.addItem(
Row.Builder()
.setOnClickListener {
val location = location(it.lon!!, it.lat!!)
surfaceRenderer.setCategoryLocation(location)
}
.setTitle(it.tags.operator.toString())
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
com.kouros.android.cars.carappservice.R.drawable.ev_station_24px
)
)
.build()
)
.addText(it.tags.network.toString())
.build()
createItem(it, category)
)
}
}
}
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.charging_station))
@@ -116,11 +98,39 @@ class CategoryScreen(
getMapActionStrip()
).build()
)
return builder.build()
}
private fun secondText(sText: String): CarText {
private fun createItem(it: Elements, category: String): Row {
var name = ""
if (it.tags.name != null) {
name = it.tags.name.toString()
}
if (name.isEmpty()) {
name = it.tags.operator.toString()
}
val row = Row.Builder()
.setOnClickListener {
val location = location(it.lon!!, it.lat!!)
surfaceRenderer.setCategoryLocation(location, category)
println(it)
}
.setTitle(name)
.setImage(carIcon(carContext, category))
if (it.distance < 1000) {
row.addText("${(it.distance).toInt()} m")
} else {
row.addText("${(it.distance / 1000).round(1)} km")
}
if (category == Constants.CHARGING_STATION) {
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
} else {
row.addText(carText("${it.tags.openingHours}"))
}
return row.build()
}
private fun carText(sText: String): CarText {
val secondText =
CarText.Builder(
"================= " + sText + " ================"

View File

@@ -1,7 +1,5 @@
package com.kouros.navigation.car.screen
import android.content.ComponentName
import android.content.Intent
import android.location.Location
import android.location.LocationManager
import android.os.CountDownTimer
@@ -9,6 +7,7 @@ import android.os.Handler
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
@@ -21,18 +20,18 @@ import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.navigation.model.MessageInfo
import androidx.car.app.navigation.model.NavigationTemplate
import androidx.car.app.navigation.model.RoutingInfo
import androidx.car.app.notification.CarPendingIntent
import androidx.car.app.suggestion.model.Suggestion
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.NavigationCarAppService
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.location
@@ -41,7 +40,6 @@ class NavigationScreen(
private var surfaceRenderer: SurfaceRenderer,
private var routeModel: RouteCarModel,
private var listener: Listener
) :
Screen(carContext) {
@@ -52,20 +50,14 @@ class NavigationScreen(
}
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
lateinit var recentPlace: Place
var recentPlaceFound = false
var recentPlaceActive = true
var calculateNewRoute = false
var recentPlace = Place()
var navigationType = NavigationType.VIEW
val viewModel = ViewModel(NavigationRepository())
val observer = Observer<String> { route ->
if (route.isNotEmpty()) {
navigationType = NavigationType.NAVIGATION
routeModel.startNavigation(route)
surfaceRenderer.setRouteData()
recentPlaceActive = false
invalidate()
}
}
@@ -73,26 +65,48 @@ class NavigationScreen(
val recentObserver = Observer<Place> { lastPlace ->
if (!routeModel.isNavigating()) {
recentPlace = lastPlace
recentPlaceFound = true
navigationType = NavigationType.RECENT
invalidate()
}
}
val placeObserver = Observer<SearchResult> { searchResult ->
val place = Place(
name = searchResult.displayName,
street = searchResult.address.road,
city = searchResult.address.city,
latitude = searchResult.lat.toDouble(),
longitude = searchResult.lon.toDouble(),
category = Constants.CONTACTS,
postalCode = searchResult.address.postcode
)
navigateToPlace(place)
}
var lastCameraSearch = 0
var speedCameras = listOf<Elements>()
val speedObserver = Observer<List<Elements>> { cameras ->
speedCameras = cameras
println("Speed cameras ${speedCameras.size}")
}
init {
viewModel.route.observe(this, observer)
viewModel.recentPlace.observe(this, recentObserver)
viewModel.loadRecentPlace(location = surfaceRenderer.lastLocation)
viewModel.placeLocation.observe(this, placeObserver)
viewModel.speedCameras.observe(this, speedObserver)
}
override fun onGetTemplate(): Template {
val actionStripBuilder = createActionStripBuilder()
if (calculateNewRoute) {
return navigationRerouteTemplate(actionStripBuilder)
}
return if (routeModel.isNavigating()) {
navigationTemplate(actionStripBuilder)
} else {
navigationEndTemplate(actionStripBuilder)
return when (navigationType) {
NavigationType.NAVIGATION -> navigationTemplate(actionStripBuilder)
NavigationType.RECENT -> navigationRecentPlaceTemplate()
NavigationType.REROUTE -> navigationRerouteTemplate(actionStripBuilder)
NavigationType.ARRIVAL -> navigationEndTemplate(actionStripBuilder)
else -> navigationViewTemplate(actionStripBuilder)
}
}
@@ -111,29 +125,36 @@ class NavigationScreen(
.build()
}
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
return NavigationTemplate.Builder()
.setBackgroundColor(CarColor.SECONDARY)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build())
.build()
}
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
if (routeModel.routeState.arrived) {
val timer = object : CountDownTimer(10000, 10000) {
val timer = object : CountDownTimer(8000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
routeModel.routeState = routeModel.routeState.copy(arrived = false)
navigationType = NavigationType.VIEW
invalidate()
}
}
timer.start()
return navigationArrivedTemplate(actionStripBuilder)
} else {
return if (recentPlaceFound && recentPlaceActive) {
return recentPlaceTemplate()
} else {
NavigationTemplate.Builder()
return NavigationTemplate.Builder()
.setBackgroundColor(CarColor.SECONDARY)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build())
.build()
}
}
}
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
var street = ""
@@ -163,7 +184,7 @@ class NavigationScreen(
.build()
}
fun recentPlaceTemplate(): Template {
fun navigationRecentPlaceTemplate(): Template {
val messageTemplate = MessageTemplate.Builder(
recentPlace.name + "\n"
+ recentPlace.city
@@ -266,6 +287,7 @@ class NavigationScreen(
}
private fun navigateAction(): Action {
navigationType = NavigationType.NAVIGATION
return Action.Builder()
.setIcon(
CarIcon.Builder(
@@ -296,9 +318,10 @@ class NavigationScreen(
.build()
)
.setOnClickListener {
recentPlaceActive = false
navigationType = NavigationType.VIEW
invalidate()
}
.setFlags(FLAG_DEFAULT)
.build()
}
@@ -368,28 +391,6 @@ class NavigationScreen(
.build()
}
private fun getSuggestion(title: Int, subtitle: Int, icon: CarIcon): Suggestion {
return Suggestion.Builder()
.setIdentifier("0")
.setTitle(carContext.getString(title))
.setSubtitle(carContext.getString(subtitle))
.setIcon(icon)
.setAction(
CarPendingIntent.getCarApp(
carContext, 0,
Intent().setComponent(
ComponentName(
carContext,
NavigationCarAppService::class.java
)
),
//.setAction(NavigationSession.EXECUTE_SCRIPT),
0
)
)
.build()
}
private fun startSearchScreen() {
screenManager
.pushForResult(
@@ -397,26 +398,39 @@ class NavigationScreen(
) { obj: Any? ->
if (obj != null) {
val place = obj as Place
val location = Location(LocationManager.GPS_PROVIDER)
location.latitude = place.latitude
location.longitude = place.longitude
viewModel.saveRecent(place)
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
currentNavigationLocation = location
routeModel.routeState.destination = place
invalidate()
if (place.longitude == 0.0) {
viewModel.findAddress(
"${obj.city} ${obj.street}},",
currentNavigationLocation
)
// result see observer
} else {
navigateToPlace(place)
}
}
}
}
fun navigateToPlace(place: Place) {
navigationType = NavigationType.VIEW
val location = location(place.longitude, place.latitude)
viewModel.saveRecent(place)
currentNavigationLocation = location
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
routeModel.routeState.destination = place
invalidate()
}
fun stopNavigation() {
navigationType = NavigationType.VIEW
listener.stopNavigation()
surfaceRenderer.routeData.value = ""
lastCameraSearch = 0
invalidate()
}
fun calculateNewRoute(destination: Place) {
calculateNewRoute = true
navigationType = NavigationType.REROUTE
stopNavigation()
invalidate()
val mainThreadHandler = Handler(carContext.mainLooper)
@@ -424,7 +438,7 @@ class NavigationScreen(
object : CountDownTimer(3000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
calculateNewRoute = false
navigationType = NavigationType.NAVIGATION
reRoute(destination)
}
}.start()
@@ -437,6 +451,12 @@ class NavigationScreen(
}
fun updateTrip(location: Location) {
if (lastCameraSearch++ % 100 == 0) {
viewModel.getSpeedCameras(location)
}
if (speedCameras.isNotEmpty()) {
updateDistance(location)
}
with(routeModel) {
updateLocation(location)
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
@@ -445,8 +465,33 @@ class NavigationScreen(
stopNavigation()
routeState = routeState.copy(arrived = true)
surfaceRenderer.routeData.value = ""
navigationType = NavigationType.ARRIVAL
invalidate()
}
}
invalidate()
}
private fun updateDistance(
location: Location,
) {
val updatedCameras = mutableListOf<Elements>()
speedCameras.forEach {
val plLocation =
location(longitude = it.lon!!, latitude = it.lat!!)
val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble()
updatedCameras.add(it)
}
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
val camera = sortedList.first()
if (camera.distance < 100) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
}
}
}
enum class NavigationType {
VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL
}

View File

@@ -7,6 +7,7 @@ import android.text.SpannableString
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Distance
@@ -22,10 +23,14 @@ import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.CONTACTS
import com.kouros.navigation.data.Constants.FAVORITES
import com.kouros.navigation.data.Constants.RECENT
import com.kouros.navigation.data.Constants.categories
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel
import kotlin.math.min
class PlaceListScreen(
@@ -62,37 +67,24 @@ class PlaceListScreen(
}
fun loadPlaces() {
if (category == Constants.RECENT) {
if (category == RECENT) {
viewModel.loadRecentPlaces(carContext, location)
}
if (category == Constants.CONTACTS) {
viewModel.loadContacts(carContext, location)
if (category == CONTACTS) {
viewModel.loadContacts(carContext)
}
if (category == Constants.FAVORITES) {
if (category == FAVORITES) {
viewModel.loadFavorites(carContext, location)
}
}
override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder()
.setNoItemsMessage(carContext.getString(R.string.no_places))
places.forEach {
itemListBuilder.addItem(
Row.Builder()
.addAction(
deleteAction(it)
)
val row = Row.Builder()
.setImage(contactIcon(it.avatar, it.category))
.setTitle(it.name!!)
.addText(SpannableString(" ").apply {
setSpan(
DistanceSpan.create(
Distance.create(
it.distance.toDouble(),
Distance.UNIT_KILOMETERS
)
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
)
})
.setOnClickListener {
val place = Place(
0,
@@ -105,30 +97,44 @@ class PlaceListScreen(
it.street,
avatar = null
)
setResult(place)
screenManager
.pushForResult(
RoutePreviewScreen(
carContext,
surfaceRenderer,
place
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
// screenManager
// .pushForResult(
// RoutePreviewScreen(
// carContext,
// surfaceRenderer,
// place
// )
// ) { obj: Any? ->
// if (obj != null) {
// setResult(obj)
// finish()
// }
// }
}
.build()
}
}
if (category != CONTACTS) {
row.addText(SpannableString(" ").apply {
setSpan(
DistanceSpan.create(
Distance.create(
it.distance.toDouble(),
Distance.UNIT_KILOMETERS
)
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
)
})
row.addAction(
deleteAction(it)
)
}
itemListBuilder.addItem(
row.build()
)
}
var title = ""
when(category) {
Constants.RECENT -> title = carContext.getString(R.string.recent_destinations)
Constants.CONTACTS -> title = carContext.getString(R.string.contacts)
Constants.FAVORITES -> title = carContext.getString(R.string.favorites)
when (category) {
RECENT -> title = carContext.getString(R.string.recent_destinations)
CONTACTS -> title = carContext.getString(R.string.contacts)
FAVORITES -> title = carContext.getString(R.string.favorites)
}
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)

View File

@@ -1,18 +1,3 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kouros.navigation.car.screen
import android.os.CountDownTimer
@@ -22,18 +7,23 @@ import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.Action.FLAG_PRIMARY
import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
import androidx.car.app.model.DurationSpan
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.navigation.model.NavigationTemplate
import androidx.car.app.navigation.model.RoutingInfo
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer
import com.kouros.data.R
@@ -78,24 +68,20 @@ class RoutePreviewScreen(
override fun onGetTemplate(): Template {
val navigateActionIcon: CarIcon = CarIcon.Builder(
IconCompat.createWithResource(
carContext, R.drawable.baseline_assistant_navigation_24
carContext, R.drawable.navigation_48px
)
).build()
val navigateAction = Action.Builder()
.setFlags(FLAG_PRIMARY)
.setFlags(FLAG_DEFAULT)
.setIcon(navigateActionIcon)
.setOnClickListener { this.onNavigate() }
.build()
val itemListBuilder = ItemList.Builder()
if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) {
itemListBuilder.addItem(createRow(0, navigateAction))
}
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.route_preview))
//.addEndHeaderAction(navigateAction)
.addEndHeaderAction(
favoriteAction()
)
@@ -104,21 +90,30 @@ class RoutePreviewScreen(
)
.build()
val timer = object : CountDownTimer(10000, 15000) {
val message = if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) {
createRouteText()
} else {
CarText.Builder("Wait")
.build()
}
val messageTemplate = MessageTemplate.Builder(
message
)
.setHeader(header)
.addAction(navigateAction)
.setLoading(message.toString() == "Wait")
.build()
val timer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
onNavigate()
//onNavigate()
}
}
timer.start()
return MapWithContentTemplate.Builder()
.setContentTemplate(
ListTemplate.Builder()
.setHeader(header)
.setSingleList(itemListBuilder.build())
.build()
)
.setContentTemplate(messageTemplate)
.setMapController(
MapController.Builder().setMapActionStrip(
getMapActionStrip()
@@ -177,18 +172,8 @@ class RoutePreviewScreen(
.build()
)
.build()
private fun createRow(index: Int, action: Action): Row {
val route: CarText = createRouteText(index)
return Row.Builder()
.setTitle(route)
.setOnClickListener { onRouteSelected(index) }
.addText("${destination.street!!} ${destination.postalCode} ${destination.city}")
.addAction(action)
.build()
}
private fun createRouteText(index: Int): CarText {
private fun createRouteText(): CarText {
val time = routeModel.route.summary.time
val length = BigDecimal(routeModel.route.distance).setScale(1, RoundingMode.HALF_EVEN)
val firstRoute = SpannableString(" \u00b7 $length km")

View File

@@ -35,7 +35,7 @@ class SearchScreen(
var categories: List<Category> = listOf(
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
//Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
)

View File

@@ -31,7 +31,6 @@ class SettingsScreen(
) : Screen(carContext) {
override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder()
listBuilder.addItem(
buildRowForTemplate(

View File

@@ -24,9 +24,11 @@ class ViewModelTest {
fun routeViewModelTest() {
val fromLocation = Location(LocationManager.GPS_PROVIDER)
fromLocation.isMock = true
fromLocation.latitude = homeLocation.latitude
fromLocation.longitude = homeLocation.longitude
val toLocation = Location(LocationManager.GPS_PROVIDER)
toLocation.isMock = true
toLocation.latitude = home2Location.latitude
toLocation.longitude = home2Location.longitude

View File

@@ -9,6 +9,7 @@ data class Elements (
@SerializedName("id" ) var id : Long? = null,
@SerializedName("lat" ) var lat : Double? = null,
@SerializedName("lon" ) var lon : Double? = null,
@SerializedName("tags" ) var tags : Tags = Tags()
@SerializedName("tags" ) var tags : Tags = Tags(),
var distance : Double = 0.0
)

View File

@@ -12,21 +12,22 @@ import java.net.URL
class Overpass {
val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
fun getAmenities(category: String, location: Location) : List<Elements> {
val boundingBox = getOverpassBbox(location, 2.0)
val bb = getBoundingBox2(location, 2.0)
fun getAmenities(type: String, category: String, location: Location) : List<Elements> {
val boundingBox = getOverpassBbox(location, 5.0)
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
"Accept",
"application/json"
)
// node["highway"="speed_camera"]
// node[amenity=$category]
httpURLConnection.setDoOutput(true);
// define a query
val test = """
// define search query
val searchQuery = """
|[out:json];
|(
| node[amenity=$category]
| node[$type=$category]
| ($boundingBox);
|);
|out body;
@@ -34,7 +35,7 @@ class Overpass {
// Send the JSON we created
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(test)
outputStreamWriter.write(searchQuery)
outputStreamWriter.flush()
// Check if the connection is successful
val responseCode = httpURLConnection.responseCode
@@ -43,7 +44,7 @@ class Overpass {
.use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
println("Overpass: $response")
println("Overpass: $type $response")
return overpass.elements
}
return emptyList()

View File

@@ -3,20 +3,22 @@ package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Tags (
@SerializedName("amenity" ) var amenity : String? = null,
@SerializedName("authentication:none" ) var authenticationNone : String? = null,
@SerializedName("capacity" ) var capacity : String? = null,
@SerializedName("motorcar" ) var motorcar : String? = null,
@SerializedName("network" ) var network : String? = null,
@SerializedName("opening_hours" ) var openingHours : String? = null,
@SerializedName("operator" ) var operator : String? = null,
@SerializedName("operator:short" ) var operatorShort : String? = null,
@SerializedName("operator:wikidata" ) var operatorWikidata : String? = null,
@SerializedName("operator:wikipedia" ) var operatorWikipedia : String? = null,
@SerializedName("ref" ) var ref : String? = null,
@SerializedName("socket:type2" ) var socketType2 : String? = null,
@SerializedName("socket:type2:output" ) var socketType2Output : String? = null
data class Tags(
@SerializedName("name") var name: String? = null,
@SerializedName("amenity") var amenity: String? = null,
@SerializedName("authentication:none") var authenticationNone: String? = null,
@SerializedName("capacity") var capacity: String? = null,
@SerializedName("motorcar") var motorcar: String? = null,
@SerializedName("network") var network: String? = null,
@SerializedName("opening_hours") var openingHours: String? = null,
@SerializedName("operator") var operator: String? = null,
@SerializedName("operator:short") var operatorShort: String? = null,
@SerializedName("operator:wikidata") var operatorWikidata: String? = null,
@SerializedName("operator:wikipedia") var operatorWikipedia: String? = null,
@SerializedName("ref") var ref: String? = null,
@SerializedName("socket:type2") var socketType2: String? = null,
@SerializedName("socket:type2:output") var socketType2Output: String? = null,
@SerializedName("maxspeed") var maxspeed: String? = null,
@SerializedName("direction") var direction: String? = null,
)

View File

@@ -36,7 +36,7 @@ class Contacts(private var context: Context) {
if (name.contains("Jola")
|| name.contains("Dominic")
|| name.contains("Martha")
|| name.contains("Rena")
|| name.contains("Groth")
|| name.contains("David")) {
val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE))
if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) {

View File

@@ -127,6 +127,7 @@ open class RouteModel() {
maneuverType = relevantManeuver.type
}
val maneuverIconPair = maneuverIcon(maneuverType)
routeState.maneuverType = maneuverIconPair.first
// Construct and return the final StepData object
return StepData(
streetName,
@@ -138,40 +139,7 @@ open class RouteModel() {
)
}
fun currentStepOld(): StepData {
val maneuver = route.currentManeuver()
var text = ""
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
val distanceStepLeft = leftStepDistance()
when (distanceStepLeft) {
in 0.0..Constants.NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
}
}
}
val type = if (hasArrived(maneuver.type)) {
maneuver.type
} else {
ManeuverType.None.value
}
var routing: (Pair<Int, Int>) = maneuverIcon(type)
when (distanceStepLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
routing = maneuverIcon(maneuverType)
}
}
}
return StepData(text, distanceStepLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance())
}
fun nextStep(): StepData {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
@@ -181,7 +149,6 @@ open class RouteModel() {
when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
}
else -> {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
@@ -319,7 +286,7 @@ open class RouteModel() {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
}
routeState.maneuverType = type
//routeState.maneuverType = type
return Pair(type, currentTurnIcon)
}
@@ -331,7 +298,7 @@ open class RouteModel() {
fun hasArrived(type: Int): Boolean {
// return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value
return type == ManeuverType.DestinationRight.value
|| routeState.maneuverType == ManeuverType.Destination.value
|| routeState.maneuverType == ManeuverType.DestinationLeft.value
|| type == ManeuverType.Destination.value
|| type == ManeuverType.DestinationLeft.value
}
}

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.model
import android.content.Context
import android.location.Geocoder
import android.location.Location
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
@@ -10,16 +9,15 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Locations
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.overpass.Overpass
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.overpass.Overpass
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor
@@ -31,34 +29,42 @@ import java.time.ZoneOffset
class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val route: MutableLiveData<String> by lazy {
MutableLiveData<String>()
MutableLiveData()
}
val previewRoute: MutableLiveData<String> by lazy {
MutableLiveData<String>()
MutableLiveData()
}
val recentPlace: MutableLiveData<Place> by lazy {
MutableLiveData<Place>()
MutableLiveData()
}
val places: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>()
MutableLiveData()
}
val favorites: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>()
MutableLiveData()
}
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
MutableLiveData<List<SearchResult>>()
MutableLiveData()
}
val placeLocation: MutableLiveData<SearchResult> by lazy {
MutableLiveData()
}
val contactAddress: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>()
MutableLiveData()
}
val elements: MutableLiveData<List<Elements>> by lazy {
MutableLiveData<List<Elements>>()
MutableLiveData()
}
val speedCameras: MutableLiveData<List<Elements>> by lazy {
MutableLiveData()
}
fun loadRecentPlace(location: Location) {
@@ -98,10 +104,16 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close()
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
if (place.latitude != 0.0) {
val distance =
repository.getRouteDistance(location, plLocation, getSearchFilter(context))
repository.getRouteDistance(
location,
plLocation,
getSearchFilter(context)
)
place.distance = distance.toFloat()
}
}
places.postValue(results)
} catch (e: Exception) {
e.printStackTrace()
@@ -164,27 +176,14 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
}
}
fun loadContacts(context: Context, currentLocation: Location) {
fun loadContacts(context: Context) {
viewModelScope.launch(Dispatchers.IO) {
try {
val geocoder = Geocoder(context)
val contactList = mutableListOf<Place>()
val contacts = Contacts(context = context)
val addresses = contacts.retrieveContacts()
for (address in addresses) {
val addressLines = address.address.split("\n")
geocoder.getFromLocationName(
address.address, 5
) {
for (adr in it) {
if (addressLines.size > 1) {
val plLocation = location(adr.longitude, adr.latitude)
val distance =
repository.getRouteDistance(
currentLocation,
plLocation,
getSearchFilter(context)
)
contactList.add(
Place(
id = address.contactId,
@@ -192,24 +191,39 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
Constants.CONTACTS,
street = addressLines[0],
city = addressLines[1],
latitude = adr.latitude,
longitude = adr.longitude,
avatar = address.avatar,
distance = distance.toFloat()
longitude = 0.0,
latitude = 0.0,
distance = 0F,
)
)
}
}
contactAddress.postValue(contactList)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fun findAddress(search: String, location: Location) {
var sortedList: List<SearchResult>
viewModelScope.launch(Dispatchers.IO) {
val placesJson = repository.searchPlaces(search, location)
val gson = GsonBuilder().serializeNulls().create()
val places = gson.fromJson(placesJson, Search::class.java)
val distPlaces = mutableListOf<SearchResult>()
places.forEach {
val plLocation =
location(longitude = it.lon.toDouble(), latitude = it.lat.toDouble())
val distance = plLocation.distanceTo(location)
it.distance = distance
distPlaces.add(it)
}
sortedList = distPlaces.sortedWith(compareBy { it.distance })
if (sortedList.isNotEmpty()) {
placeLocation.postValue(sortedList.first())
}
}
}
fun searchPlaces(search: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
val placesJson = repository.searchPlaces(search, location)
@@ -223,7 +237,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
it.distance = distance
distPlaces.add(it)
}
val sortedList = distPlaces.sortedWith(compareBy({ it.distance }))
val sortedList = distPlaces.sortedWith(compareBy { it.distance })
searchPlaces.postValue(sortedList)
}
}
@@ -237,8 +251,33 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun getAmenities(category: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities(category, location)
elements.postValue(amenities)
val amenities = Overpass().getAmenities("amenity", category, location)
val distAmenities = mutableListOf<Elements>()
amenities.forEach {
val plLocation =
location(longitude = it.lon!!, latitude = it.lat!!)
val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble()
distAmenities.add(it)
}
val sortedList = distAmenities.sortedWith(compareBy { it.distance })
elements.postValue(sortedList)
}
}
fun getSpeedCameras(location: Location) {
viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities("highway", "speed_camera", location)
val distAmenities = mutableListOf<Elements>()
amenities.forEach {
val plLocation =
location(longitude = it.lon!!, latitude = it.lat!!)
val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble()
distAmenities.add(it)
}
val sortedList = distAmenities.sortedWith(compareBy { it.distance })
speedCameras.postValue(sortedList)
}
}

View File

@@ -2,6 +2,8 @@ package com.kouros.navigation.utils
import android.location.Location
import com.kouros.navigation.data.BoundingBox
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Feature
@@ -102,12 +104,12 @@ object GeoUtils {
return featureCollection.toJson()
}
fun createPointCollection(lineCoordinates: List<List<Double>>): String {
fun createPointCollection(lineCoordinates: List<List<Double>>, category: String): String {
val featureCollection = buildFeatureCollection {
lineCoordinates.forEach {
addFeature {
geometry = org.maplibre.spatialk.geojson.Point(it[0], it[1])
properties = null
properties = buildJsonObject { put("category", category) }
}
}
}

View File

@@ -4,19 +4,7 @@ import android.content.Context
import android.location.Location
import android.location.LocationManager
import androidx.core.content.edit
import com.kouros.navigation.data.BoundingBox
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.dsl.addFeature
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
import org.maplibre.spatialk.geojson.dsl.buildLineString
import org.maplibre.spatialk.geojson.toJson
import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
@@ -24,7 +12,6 @@ import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import kotlin.math.absoluteValue
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.time.Duration
@@ -33,7 +20,7 @@ import kotlin.time.Duration.Companion.seconds
object NavigationUtils {
fun getBooleanKeyValue(context: Context, key: String) : Boolean {
fun getBooleanKeyValue(context: Context, key: String): Boolean {
return context
.getSharedPreferences(
SHARED_PREF_KEY,
@@ -56,7 +43,7 @@ object NavigationUtils {
}
}
fun getIntKeyValue(context: Context, key: String) : Int {
fun getIntKeyValue(context: Context, key: String): Int {
return context
.getSharedPreferences(
SHARED_PREF_KEY,
@@ -88,9 +75,8 @@ fun calculateZoom(speed: Double?): Double {
val zoom = when (speedKmh) {
in 0..10 -> 18.0
in 11..30 -> 17.0
in 21..40 -> 16.0
in 31..50 -> 15.0
in 51..60 -> 15.0
in 31..50 -> 16.0
in 61..70 -> 15.0
else -> 14
}
return zoom.toDouble()
@@ -98,30 +84,25 @@ fun calculateZoom(speed: Double?): Double {
fun previewZoom(previewDistance: Double): Double {
when (previewDistance) {
in 0.0..10.0 -> {
return 13.0
}
in 10.0..20.0 -> {
return 11.0
}
in 20.0..30.0 -> {
return 10.0
}
in 0.0..10.0 -> return 13.0
in 10.0..20.0 -> return 11.0
in 20.0..30.0 -> return 10.0
}
return 9.0
}
fun calcTilt(newZoom: Double, tilt: Double): Double = if (newZoom < 13) {
fun calculateTilt(newZoom: Double, tilt: Double): Double =
if (newZoom < 13) {
0.0
} else {
} else {
if (tilt == 0.0) {
55.0
} else {
tilt
}
}
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double) : Double {
}
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double): Double {
val distance = fromLocation.distanceTo(toLocation)
if (distance < 1.0) {
return oldBearing
@@ -130,7 +111,7 @@ fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double) :
return bearing
}
fun location(longitude : Double, latitude: Double): Location {
fun location(longitude: Double, latitude: Double): Location {
val location = Location(LocationManager.GPS_PROVIDER)
location.longitude = longitude
location.latitude = latitude
@@ -138,7 +119,7 @@ fun location(longitude : Double, latitude: Double): Location {
}
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 zdt = ZonedDateTime.of(dateTime, ZoneId.of("Europe/Berlin"))
return zdt.format(dateFormatter)

View File

@@ -0,0 +1,10 @@
<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="M220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.71,468 649.36,482.64Q664,497.29 664,518L664,737Q664,759 681.5,773.5Q699,788 722,788Q745,788 765,773.5Q785,759 785,737L785,350L770,350Q757.25,350 748.63,341.37Q740,332.75 740,320L740,230L760,230L760,180L790,180L790,230L830,230L830,180L860,180L860,230L880,230L880,320Q880,332.75 871.38,341.37Q862.75,350 850,350L835,350L835,736.69Q835,780 801,810Q767,840 721.82,840Q677.66,840 645.83,810Q614,780 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM337,746L425,606L372,606L372,501L285,641L337,641L337,746Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<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>

View File

@@ -0,0 +1,10 @@
<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,404Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<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>

View File

@@ -0,0 +1,10 @@
<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="M120,840L120,780L207,525L120,270L120,210L647,210L709,40L777,67L725,210L840,210L840,270L752,525L840,780L840,840L120,840ZM452,679L512,679L512,555L636,555L636,495L512,495L512,371L452,371L452,495L328,495L328,555L452,555L452,679Z"/>
</vector>

View File

@@ -6,7 +6,7 @@ koinAndroid = "4.1.1"
koinAndroidxCompose = "4.1.1"
koinComposeViewmodel = "4.1.1"
koinCore = "4.1.1"
kotlin = "2.2.21"
kotlin = "2.3.0"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"