Nominatim

This commit is contained in:
Dimitris
2025-11-27 10:34:18 +01:00
parent ce382e304c
commit c79fd157e4
51 changed files with 827 additions and 285 deletions

View File

@@ -1,9 +1,6 @@
package com.kouros.navigation.car
import android.R.attr.strokeWidth
import android.graphics.Rect
import android.location.Location
import androidx.car.app.CarContext
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
@@ -18,7 +15,6 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
@@ -29,8 +25,8 @@ import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.data.NavigationColor
import com.kouros.navigation.data.RouteColor
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
@@ -38,8 +34,10 @@ import org.maplibre.compose.expressions.dsl.const
import org.maplibre.compose.layers.Anchor
import org.maplibre.compose.layers.FillLayer
import org.maplibre.compose.layers.LineLayer
import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.location.UserLocationState
import org.maplibre.compose.sources.GeoJsonData
import org.maplibre.compose.sources.Source
import org.maplibre.compose.sources.rememberGeoJsonSource
@@ -47,8 +45,8 @@ import org.maplibre.spatialk.geojson.Position
@Composable
fun cameraState(position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
val padding = getPaddingValues(preview)
fun cameraState(width: Int, height: Int, position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
val padding = getPaddingValues(width, height, preview)
return rememberCameraState(
firstPosition =
CameraPosition(
@@ -77,7 +75,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
LineLayer(
id = "routes",
source = routes,
color = const(Color.Blue),
color = const(RouteColor),
width = const(14.dp),
)
}
@@ -93,7 +91,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
LineLayer(
id = "routes-pre",
source = routes,
color = const(Color.Cyan),
color = const(RouteColor),
width = const(4.dp),
)
}
@@ -116,7 +114,7 @@ fun DrawImage(location: Location) {
val textMeasurer = rememberTextMeasurer()
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
val painter = rememberVectorPainter(image = vector)
val tint = remember { ColorFilter.tint(Color.Blue) }
val tint = remember { ColorFilter.tint(NavigationColor) }
Box(
modifier = Modifier
@@ -138,11 +136,11 @@ fun DrawImage(location: Location) {
val measuredText =
textMeasurer.measure(
AnnotatedString("${(location.speed * 3.6).toInt()}"),
style = TextStyle(fontSize = 22.sp)
style = TextStyle(color = Color.White, fontSize = 22.sp)
)
onDrawBehind {
drawCircle(
Color.LightGray, radius = 30.dp.toPx(), center = Offset(5f, 10f)
Color.Black, radius = 30.dp.toPx(), center = Offset(15f, 12f)
)
drawText(measuredText)
}
@@ -167,8 +165,23 @@ fun Puck(cameraState: CameraState, location: Location) {
)
}
@Composable
fun PuckState(cameraState: CameraState, userLocationState: UserLocationState) {
LocationPuck(
idPrefix = "user-location1",
locationState = userLocationState,
cameraState = cameraState,
accuracyThreshold = 10f,
showBearing = false,
sizes = LocationPuckSizes(dotRadius = 10.dp),
colors = LocationPuckColors(
dotFillColorCurrentLocation = Color.Cyan,
accuracyStrokeColor = Color.Green
)
)
}
fun getPaddingValues(preView: Boolean): PaddingValues {
fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
val padding = PaddingValues(start = 100.dp, top = 300.dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
return if (preView) {

View File

@@ -22,14 +22,16 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.utils.NavigationUtils.snapLocation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class NavigationSession : Session() {
class NavigationSession : Session(), NavigationScreen.Listener {
val uriScheme = "samples";
val uriHost = "navigation";
@@ -40,7 +42,7 @@ class NavigationSession : Session() {
lateinit var surfaceRenderer: SurfaceRenderer
var locationIndex = 100
var locationIndex = 0
val simulate = true
@@ -83,11 +85,10 @@ class NavigationSession : Session() {
override fun onCreateScreen(intent: Intent): Screen {
routeModel = RouteCarModel()
ObjectBox.init(carContext);
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel)
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this)
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
@@ -159,7 +160,7 @@ class NavigationSession : Session() {
updateLocation(location)
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
/* minTimeMs= */ 10,
/* minTimeMs= */ 100,
/* minDistanceM= */ 0f,
mLocationListener
)
@@ -183,8 +184,14 @@ class NavigationSession : Session() {
)
val loc = routeModel.route.waypoints[locationIndex]
val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0] + 0.0003
curLocation.latitude = loc[1] + 0.0002
if ( locationIndex == 1500) {
curLocation.longitude = loc[0] + 0.003
curLocation.latitude = loc[1] + 0.003
} else {
curLocation.longitude = loc[0]
curLocation.latitude = loc[1]
}
curLocation.speed = 15F
update(curLocation)
locationIndex += 1
if (locationIndex > routeModel.route.waypoints.size) {
@@ -203,6 +210,13 @@ class NavigationSession : Session() {
routeModel.updateLocation(location)
navigationScreen.updateTrip()
}
surfaceRenderer.updateLocation(location)
val result = surfaceRenderer.updateLocation(location)
if (!result) {
navigationScreen.stopNavigation()
}
}
override fun stopNavigation() {
routeModel.stopNavigation()
}
}

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.car
import android.app.Presentation
import android.content.res.Resources.getSystem
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
@@ -12,6 +11,7 @@ import androidx.car.app.AppManager
import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -25,11 +25,13 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.location
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.map.MaplibreMap
@@ -59,11 +61,9 @@ class SurfaceRenderer(
var stableArea = Rect()
var containerWidth = 0
var width = 0
var containerHeight = 0
var containerDpi = 1
var height = 0
var lastBearing = 0.0
val routeData = MutableLiveData("")
@@ -107,17 +107,8 @@ class SurfaceRenderer(
0
)
containerWidth = surfaceContainer.width
containerHeight = surfaceContainer.height
if (surfaceContainer.dpi != 0) {
containerDpi = surfaceContainer.dpi
}
println(surfaceContainer.toString())
println(containerDpi)
println(carContext.resources.displayMetrics.density)
println(carContext.resources.displayMetrics.densityDpi)
println(getSystem().displayMetrics.density)
println(getSystem().displayMetrics.densityDpi)
width = surfaceContainer.width
height = surfaceContainer.height
mapView = ComposeView(carContext).apply {
this.setViewTreeLifecycleOwner(lifecycleOwner)
@@ -175,15 +166,18 @@ class SurfaceRenderer(
val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState()
val cameraState = cameraState(position, tilt, preview)
val cameraState = cameraState(width, height, position, tilt, preview)
val baseStyle =
if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
Constants.STYLE
)
MaplibreMap(
cameraState = cameraState,
baseStyle = BaseStyle.Uri(Constants.STYLE),
baseStyle = baseStyle,
) {
getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)
&& Constants.STYLE.contains("liberty")) {
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)) {
BuildingLayer(tiles)
}
RouteLayer(route, previewRoute)
@@ -193,43 +187,36 @@ class SurfaceRenderer(
ShowPosition(cameraState, position)
}
@Composable
fun ShowPosition(cameraState: CameraState, position: CameraPosition?) {
var cameraDuration = duration(position)
var bearing = position!!.bearing
var zoom = position.zoom
var target = position.target
var localTilt = tilt
if (!preview) {
DrawImage(lastLocation)
val cameraDuration = duration(position)
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = position!!.bearing,
zoom = position.zoom,
target = position.target,
tilt = tilt,
padding = getPaddingValues(preview)
),
duration = cameraDuration
)
}
} else {
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = 0.0,
zoom = 11.0,
target = Position(centerLocation.longitude, centerLocation.latitude),
tilt = 0.0,
padding = getPaddingValues(preview)
),
duration = 3.seconds
)
}
cameraDuration = 3.seconds
bearing = 0.0
zoom = 11.0
target = Position(centerLocation.longitude, centerLocation.latitude)
localTilt = 0.0
}
LaunchedEffect(position) {
cameraState.animateTo(
finalPosition = CameraPosition(
bearing = bearing,
zoom = zoom,
target = target,
tilt = localTilt,
padding = getPaddingValues(width, height, preview)
),
duration = cameraDuration
)
}
}
override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "SurfaceRenderer created")
carContext.getCarService(AppManager::class.java)
@@ -263,55 +250,56 @@ class SurfaceRenderer(
}
}
fun updateLocation(location: Location) {
fun updateLocation(location: Location) : Boolean {
synchronized(this) {
if (!preview) {
var snapedLocation = location
var bearing: Double
bearing = cameraPosition.value!!.bearing
if (routeModel.isNavigating()) {
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
// stimmt nicht
//bearing = routeModel.currentStep().bearing
}
bearing = cameraPosition.value!!.bearing
if (lastLocation.latitude != snapedLocation.latitude) {
if (lastLocation.distanceTo(snapedLocation) > 5) {
bearing = lastLocation.bearingTo(snapedLocation).toDouble()
bearing = routeModel.currentStep().bearing
if (snapedLocation.longitude == 0.0) {
//reRoute()
return false
}
}
val zoom = if (!panView) {
calculateZoom(snapedLocation.speed.toDouble())
} else {
cameraPosition.value!!.zoom
}
updateCameraPosition(bearing, zoom, Position(snapedLocation.longitude, snapedLocation.latitude))
lastBearing = cameraPosition.value!!.bearing
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
padding = getPaddingValues(preview),
target = Position(snapedLocation.longitude, snapedLocation.latitude),
)
)
lastLocation = snapedLocation
} else {
val bearing = 0.0
val zoom = 14.0
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
tilt = 0.0,
target = Position(centerLocation.longitude, centerLocation.latitude)
)
updateCameraPosition(
bearing,
zoom,
Position(centerLocation.longitude, centerLocation.latitude)
)
}
}
return true
}
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
tilt = 0.0,
padding = getPaddingValues(width, height, preview),
target = target
)
)
}
fun setRouteData() {
routeData.value = routeModel.route.routeGeoJson
previewRouteData.value = ""
preview = false
panView = false
}

View File

@@ -29,9 +29,9 @@ import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate
import androidx.core.graphics.drawable.IconCompat
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType
import com.kouros.navigation.model.RouteModel
import org.json.JSONObject
import java.util.TimeZone
import java.util.concurrent.TimeUnit
@@ -52,8 +52,8 @@ class RouteCarModel() : RouteModel() {
routingData(ManeuverType.None.value, carContext)
}
when (stepData.leftDistance) {
in 0.0..100.0 -> {
if (route.currentIndex < route.maneuvers.size) {
in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
routing = routingData(maneuverType, carContext)
@@ -76,7 +76,7 @@ class RouteCarModel() : RouteModel() {
}
/** Returns the next [Step] with information such as the cue text and images. */
fun nextStep(carContext: CarContext): Step {
fun nextStep(carContext: CarContext): Step? {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
val routing = routingData(maneuverType, carContext)
@@ -84,10 +84,11 @@ class RouteCarModel() : RouteModel() {
val distanceLeft = leftStepDistance() * 1000
when (distanceLeft) {
in 0.0..100.0 -> {
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
text = maneuver.streetNames!![0]
}
in 0.0..NEXT_STEP_THRESHOLD -> {
return null
// if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
// text = maneuver.streetNames!![0]
// }
}
else -> {

View File

@@ -24,6 +24,7 @@ import androidx.lifecycle.Observer
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.NavigationCarAppService
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
@@ -32,10 +33,18 @@ import com.kouros.navigation.model.ViewModel
class NavigationScreen(
carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer,
private var routeModel: RouteCarModel
private var routeModel: RouteCarModel,
private var listener: Listener
) :
Screen(carContext) {
/** A listener for navigation start and stop signals. */
interface Listener {
/** Stops navigation. */
fun stopNavigation()
}
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
val vieModel = ViewModel(NavigationRepository())
val observer = Observer<String> { route ->
@@ -77,7 +86,7 @@ class NavigationScreen(
}
}
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate{
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
actionStripBuilder.addAction(
Action.Builder()
.setTitle(carContext.getString(R.string.stop_action_title))
@@ -91,9 +100,7 @@ class NavigationScreen(
.build()
)
.setOnClickListener {
routeModel.stopNavigation()
surfaceRenderer.routeData.postValue("")
invalidate()
stopNavigation()
}
.build()
)
@@ -107,16 +114,17 @@ class NavigationScreen(
.setBackgroundColor(CarColor.GREEN)
.build()
}
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate{
if (routeModel.isArrived()) {
val timer = object: CountDownTimer(10000, 10000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
routeModel.arrived = false
invalidate()
}
}
timer.start()
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
if (routeModel.isArrived()) {
val timer = object : CountDownTimer(10000, 10000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
routeModel.arrived = false
invalidate()
}
}
timer.start()
return NavigationTemplate.Builder()
.setNavigationInfo(
MessageInfo.Builder(
@@ -139,7 +147,7 @@ class NavigationScreen(
.setMapActionStrip(mapActionStripBuilder().build())
.build()
} else {
return NavigationTemplate.Builder()
return NavigationTemplate.Builder()
.setBackgroundColor(CarColor.SECONDARY)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build())
@@ -149,19 +157,29 @@ class NavigationScreen(
fun getRoutingInfo(): RoutingInfo {
var currentDistance = routeModel.currentDistance
val displayUnit = if (currentDistance > 1000.0) {
currentDistance /= 1000.0
Distance.UNIT_KILOMETERS
} else {
Distance.UNIT_METERS
}
val displayUnit = if (currentDistance > 1000.0) {
currentDistance /= 1000.0
Distance.UNIT_KILOMETERS
} else {
Distance.UNIT_METERS
}
val nextStep = routeModel.nextStep(carContext = carContext)
if (nextStep != null) {
return RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit)
)
.setNextStep(routeModel.nextStep(carContext = carContext))
.setNextStep(nextStep)
.build()
} else {
return RoutingInfo.Builder()
.setCurrentStep(
routeModel.currentStep(carContext = carContext),
Distance.create(currentDistance, displayUnit)
)
.build()
}
}
@@ -196,23 +214,22 @@ class NavigationScreen(
surfaceRenderer.handleScale(-1)
}
.build())
if (surfaceRenderer.panView)
{
if (surfaceRenderer.panView) {
actionStripBuilder.addAction(
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_pan_24
)
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_pan_24
)
.build()
).setOnClickListener {
surfaceRenderer.panView = false
}
.build()
)
)
.build()
).setOnClickListener {
surfaceRenderer.panView = false
}
.build()
)
}
return actionStripBuilder
}
@@ -258,9 +275,15 @@ class NavigationScreen(
}
}
fun stopNavigation() {
listener.stopNavigation()
surfaceRenderer.routeData.postValue("")
invalidate()
}
fun reRoute() {
//NavigationMessage(carContext).createAlert()
//vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
NavigationMessage(carContext).createAlert()
vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
}
fun updateTrip() {

View File

@@ -71,6 +71,7 @@ class RoutePreviewScreen(
routeModel.startNavigation(route)
surfaceRenderer.setPreviewRouteData(routeModel)
val geocoder = Geocoder(carContext)
// nominatim ->
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
for (address in it) {
street = address.getAddressLine(0)

View File

@@ -1,9 +1,7 @@
package com.kouros.navigation.car.screen
import android.annotation.SuppressLint
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
@@ -12,12 +10,15 @@ import androidx.car.app.model.Row
import androidx.car.app.model.SearchTemplate
import androidx.car.app.model.SearchTemplate.SearchCallback
import androidx.car.app.model.Template
import androidx.lifecycle.Observer
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox
import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.model.ViewModel
class SearchScreen(
@@ -28,13 +29,25 @@ class SearchScreen(
var isSearchComplete: Boolean = false
var isSearching: Boolean = false
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))
)
lateinit var searchResult: Search
val observer = Observer<Search> { newSearch ->
println(newSearch)
searchResult = newSearch
invalidate()
}
val viewModel = ViewModel(NavigationRepository())
init {
viewModel.searchPlaces.observe(this, observer)
}
override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder()
@@ -42,7 +55,7 @@ class SearchScreen(
val searchItemListBuilder = ItemList.Builder()
.setNoItemsMessage("No search results to show")
if (!isSearching) {
if (!isSearchComplete) {
categories.forEach {
it.name
itemListBuilder.addItem(
@@ -68,9 +81,11 @@ class SearchScreen(
.build()
)
}
} else {
doSearch(searchItemListBuilder)
}
val itemList = if (!isSearching) {
val itemList = if (!isSearchComplete) {
itemListBuilder.build()
} else {
searchItemListBuilder.build()
@@ -79,12 +94,12 @@ class SearchScreen(
return SearchTemplate.Builder(
object : SearchCallback {
override fun onSearchTextChanged(searchText: String) {
doSearch(searchText, searchItemListBuilder)
//doSearch(searchText, searchItemListBuilder)
}
override fun onSearchSubmitted(searchTerm: String) {
isSearchComplete = true
doSearch(searchTerm, searchItemListBuilder)
viewModel.searchPlaces(searchTerm)
}
})
.setHeaderAction(Action.BACK)
@@ -94,55 +109,35 @@ class SearchScreen(
}
@SuppressLint("DefaultLocale")
fun doSearch(searchText: String, searchItemListBuilder: ItemList.Builder) {
isSearching = true
val geocoder = Geocoder(carContext)
val box = getBoundingBox(location.latitude, location.longitude, 100.0)
val lowerLeft = box["sw"]
val lowerLeftLat = lowerLeft!!["lat"]!!
val lowerLeftLon = lowerLeft["lon"]!!
val upperRight = box["ne"]
val upperRightLat = upperRight!!["lat"]!!
val upperRightLon = upperRight["lon"]!!
val addressLocation = Location(LocationManager.GPS_PROVIDER)
geocoder.getFromLocationName(
searchText, 5,
//lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
) {
for (address in it) {
val name: String = address.getAddressLine(0)
addressLocation.latitude = address.latitude
addressLocation.longitude = address.longitude
val distance = location.distanceTo(addressLocation)
val dist = String.format("%.1f", (distance / 1000))
searchItemListBuilder.addItem(
Row.Builder()
.setTitle("$name $dist km")
.setOnClickListener {
val place = Place(
0,
name,
name,
address.latitude,
address.longitude,
"0",
"city",
name
)
setResult(place)
finish()
}
.setBrowsable(false)
.build()
)
}
fun doSearch(searchItemListBuilder: ItemList.Builder) {
searchResult.forEach {
println(it.displayName)
//val name: String = address.getAddressLine(0)
//addressLocation.latitude = address.latitude
//adressLocation.longitude = address.longitude
//val distance = location.distanceTo(addressLocation)
//val dist = String.format("%.1f", (distance / 1000))
searchItemListBuilder.addItem(
Row.Builder()
.setTitle(it.displayName)
.setOnClickListener {
val place = Place(
name = it.displayName,
latitude = it.lat.toDouble(),
longitude = it.lon.toDouble(),
street = it.address.road,
city = it.address.city,
postalCode = it.address.postcode
)
setResult(place)
finish()
}
.setBrowsable(false)
.build()
)
}
if (searchText.isEmpty()) {
isSearching = false
}
val itemList = searchItemListBuilder.build()
// val itemList = searchItemListBuilder.build()
invalidate()
}
}

View File

@@ -21,7 +21,6 @@
<item name="carColorPrimaryDark">#5904DF</item>
<item name="carColorSecondary">#328E10</item>
<item name="carColorSecondaryDark">#1A6004</item>
<item name="carPermissionActivityLayout">@layout/permission_request</item>
<item name="markerIconTintColor">#FF7F39FB</item>
<item name="markerIconTintColorDark">#FF5904DF</item>
</style>

View File

@@ -9,21 +9,20 @@ import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Example local unit test, which will execute on the development machine (host).
*
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
class ViewModelTest {
val repo = NavigationRepository()
val viewModel = ViewModel(repo)
val model = RouteModel()
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
val model = RouteModel()
val repo = NavigationRepository()
val viewModel = ViewModel(repo)
fun routeViewModelTest() {
val fromLocation = Location(LocationManager.GPS_PROVIDER)
fromLocation.latitude = homeLocation.latitude
fromLocation.longitude = homeLocation.longitude

View File

@@ -0,0 +1,7 @@
package com.kouros.navigation.data
import androidx.compose.ui.graphics.Color
val NavigationColor = Color(0xFF1965D9)
val RouteColor = Color(0xFF2E75E1)

View File

@@ -119,9 +119,10 @@ data class ValhallaLocation (
object Constants {
const val STYLE: String = "https://kouros-online.de/liberty.json"
const val STYLE_DARK: String = "https://kouros-online.de/liberty_night.json"
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
//const val STYLE: String = "https://tiles.openfreemap.org/styles/dark"
const val TAG: String = "Navigation"
const val CONTACTS: String = "Contacts"
@@ -145,6 +146,10 @@ object Constants {
const val SHOW_THREED_BUILDING = "Show3D"
const val NEXT_STEP_THRESHOLD = 100.0
const val MAXIMAL_ROUTE_DEVIATION = 70.0
}

View File

@@ -32,6 +32,7 @@ class NavigationRepository {
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
private val nominatimUrl = "https://nominatim.openstreetmap.org/search?q="
fun getRoute(currentLocation : Location, location: Location): String {
val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
@@ -45,7 +46,7 @@ class NavigationRepository {
language = "de-DE"
)
val routeLocation = Json.encodeToString(valhallaLocation)
return fetchUrl(routeUrl + routeLocation)
return fetchUrl(routeUrl + routeLocation, true)
}
fun getRouteDistance(currentLocation : Location, location: Location): Double {
@@ -55,9 +56,13 @@ class NavigationRepository {
return routeModel.route.distance
}
fun searchPlaces(search : String) : String {
return fetchUrl("$nominatimUrl$search&format=jsonv2&addressdetails=true&countrycodes=de", false)
}
fun getPlaces(): List<Place> {
val places: MutableList<Place> = ArrayList()
val placesStr = fetchUrl(placesUrl)
val placesStr = fetchUrl(placesUrl, true)
val jArray = JSONArray(placesStr)
for (i in 0..<jArray.length()) {
val json = jArray.getJSONObject(i)
@@ -77,22 +82,25 @@ class NavigationRepository {
}
private fun fetchUrl(url: String): String {
private fun fetchUrl(url: String, authenticator : Boolean): String {
try {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(
"kouros",
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
)
}
})
if (authenticator) {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(
"kouros",
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
)
}
})
}
println(url)
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
httpURLConnection.setRequestProperty(
"Accept",
"application/json"
) // The format of response we want to get from the server
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
httpURLConnection.requestMethod = "GET"
val responseCode = httpURLConnection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
@@ -101,7 +109,7 @@ class NavigationRepository {
return response
}
} catch (e: Exception) {
println(e.message)
println("Exception ${e.message}")
}
return ""
}

View File

@@ -45,7 +45,7 @@ data class Route (
var routeGeoJson : String,
var currentIndex: Int
var currentManeuverIndex: Int
) {
@@ -103,10 +103,10 @@ data class Route (
}
fun currentManeuver() : Maneuvers {
return maneuvers[currentIndex]
return maneuvers[currentManeuverIndex]
}
fun nextManeuver() : Maneuvers {
return maneuvers[currentIndex+1]
return maneuvers[currentManeuverIndex+1]
}
}

View File

@@ -0,0 +1,18 @@
package com.kouros.navigation.data.nominatim
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Address(
@SerializedName("city") var city: String = "",
@SerializedName("country") var country: String = "",
@SerializedName("country_code") var countryCode: String = "",
@SerializedName("neighbourhood") var neighbourhood: String = "",
@SerializedName("postcode") var postcode: String = "",
@SerializedName("road") var road: String = "",
@SerializedName("state") var state: String = "",
@SerializedName("suburb") var suburb: String = "",
)

View File

@@ -0,0 +1,30 @@
package com.kouros.navigation.data.nominatim
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
class Search : ArrayList<SearchResult>()
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class SearchResult(
@SerializedName("place_id") var placeId: Int = 0,
@SerializedName("licence") var licence: String = "",
@SerializedName("osm_type") var osmType: String = "",
@SerializedName("osm_id") var osmId: Long = 0,
@SerializedName("lat") var lat: String = "",
@SerializedName("lon") var lon: String = "",
@SerializedName("category") var category: String = "",
@SerializedName("type") var type: String = "",
@SerializedName("place_rank") var placeRank: Int = 0,
@SerializedName("importance") var importance: Double = 0.0,
@SerializedName("addresstype") var addresstype: String = "",
@SerializedName("address") var address: Address,
@SerializedName("name") var name: String = "",
@SerializedName("display_name") var displayName: String = "",
@SerializedName("boundingbox") var boundingbox: ArrayList<String> = arrayListOf()
)

View File

@@ -2,11 +2,10 @@ package com.kouros.navigation.model
import android.location.Location
import android.location.LocationManager
import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
@@ -26,9 +25,9 @@ open class RouteModel() {
var maneuverType = 0
/*
Index in a maneuver
current shapeIndex
*/
var currentIndex = 0
private var currentShapeIndex = 0
var distanceToStepEnd = 0F
@@ -64,7 +63,7 @@ open class RouteModel() {
fun updateLocation(location: Location) {
var nearestDistance = 100000.0f
route.currentIndex = -1
route.currentManeuverIndex = -1
// find maneuver
for ((i, maneuver) in route.maneuvers.withIndex()) {
val beginShapeIndex = maneuver.beginShapeIndex
@@ -72,7 +71,7 @@ open class RouteModel() {
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
if (distance < nearestDistance) {
nearestDistance = distance
route.currentIndex = i
route.currentManeuverIndex = i
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
}
}
@@ -84,13 +83,13 @@ open class RouteModel() {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
if (bearing == 0F) {
bearing = maneuver.bearingAfter.toFloat()
}
val curLocation = location(route.pointLocations[currentShapeIndex].latitude(), route.pointLocations[currentShapeIndex].longitude())
val nextLocation = location(route.pointLocations[currentShapeIndex+1].latitude(), route.pointLocations[currentShapeIndex+1].longitude())
bearing = curLocation.bearingTo(nextLocation)
val distanceStepLeft = leftStepDistance() * 1000
when (distanceStepLeft) {
in 0.0..100.0 -> {
if (route.currentIndex < route.maneuvers.size) {
in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
@@ -109,13 +108,13 @@ open class RouteModel() {
) {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = route.waypoints[i][0]
polylineLocation.latitude = route.waypoints[i][1]
val distance: Float = location.distanceTo(polylineLocation)
val waypoint = Location(LocationManager.GPS_PROVIDER)
waypoint.longitude = route.waypoints[i][0]
waypoint.latitude = route.waypoints[i][1]
val distance: Float = location.distanceTo(waypoint)
if (distance < nearestLocation) {
nearestLocation = distance
currentIndex = i
currentShapeIndex = i
beginIndex = beginShapeIndex
endIndex = endShapeIndex
distanceToStepEnd = 0F
@@ -157,14 +156,14 @@ open class RouteModel() {
fun travelLeftTime(): Double {
var timeLeft = 0.0
for (i in route.currentIndex + 1..<route.maneuvers.size) {
for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) {
val maneuver = route.maneuvers[i]
timeLeft += maneuver.time
}
if (endIndex > 0) {
val maneuver = route.currentManeuver()
val curTime = maneuver.time
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
val time = curTime * percent / 100
timeLeft += time
}
@@ -183,14 +182,14 @@ open class RouteModel() {
fun travelLeftDistance(): Double {
var leftDistance = 0.0
for (i in route.currentIndex + 1..<route.maneuvers.size) {
for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) {
val maneuver = route.maneuvers[i]
leftDistance += maneuver.length
}
if (endIndex > 0) {
val maneuver = route.currentManeuver()
val curDistance = maneuver.length
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
val time = curDistance * percent / 100
leftDistance += time
}
@@ -208,10 +207,9 @@ open class RouteModel() {
fun stopNavigation() {
route.clear()
navigating = false
currentIndex = 0
currentShapeIndex = 0
distanceToStepEnd = 0F
beginIndex = 0
endIndex = 0
}
}

View File

@@ -3,16 +3,16 @@ package com.kouros.navigation.model
import android.content.Context
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers
@@ -32,6 +32,10 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData<List<Place>>()
}
val searchPlaces: MutableLiveData<Search> by lazy {
MutableLiveData<Search>()
}
val contactAddress: MutableLiveData<List<Place>> by lazy {
MutableLiveData<List<Place>>()
}
@@ -85,11 +89,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
for (address in addresses) {
val addressLines = address.address.split("\n")
geocoder.getFromLocationName(
address.address, 5) {
address.address, 5
) {
for (adr in it) {
if (addressLines.size > 1) {
val plLocation = location(adr.latitude, adr.longitude)
val distance = repository.getRouteDistance(currentLocation, plLocation)
val distance =
repository.getRouteDistance(currentLocation, plLocation)
contactList.add(
Place(
id = address.contactId,
@@ -115,6 +121,15 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
}
}
fun searchPlaces(search: String) {
viewModelScope.launch(Dispatchers.IO) {
val placesJson = repository.searchPlaces(search)
val gson = GsonBuilder().serializeNulls().create()
val places = gson.fromJson(placesJson, Search::class.java)
searchPlaces.postValue(places)
}
}
fun saveRecent(place: Place) {
viewModelScope.launch(Dispatchers.IO) {
place.category = Constants.RECENT

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.location.Location
import android.location.LocationManager
import androidx.core.content.edit
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.GeoJsonFeature
@@ -57,11 +58,17 @@ object NavigationUtils {
}
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
val oldLocation = location(location.latitude, location.longitude)
if (stepCoordinates.size > 1) {
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
val point = pointFeature.geometry() as Point
location.latitude = point.latitude()
location.longitude = point.longitude()
val distance = oldLocation.distanceTo(location)
if (distance > MAXIMAL_ROUTE_DEVIATION) {
println("Distance to big")
return location(0.0, 0.0)
}
}
return location
}