This commit is contained in:
Dimitris
2025-12-16 07:19:29 +01:00
parent 72872cddeb
commit d546ede0e5
22 changed files with 589 additions and 137 deletions

View File

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

View File

@@ -45,6 +45,7 @@ import androidx.lifecycle.Observer
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.homeLocation
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.StepData
@@ -110,18 +111,19 @@ class MainActivity : ComponentActivity() {
private var loadRecentPlaces = false
private var overpass = false
init {
viewModel.route.observe(this, observer)
}
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (useMock) {
checkMockLocationEnabled()
}
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
if (useMock) {
@@ -241,6 +243,10 @@ 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()) {

View File

@@ -21,6 +21,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
@@ -64,27 +65,18 @@ class SurfaceRenderer(
)
var stableArea = Rect()
var width = 0
var height = 0
var lastBearing = 0.0
val routeData = MutableLiveData("")
val previewRouteData = MutableLiveData("")
val speed = MutableLiveData(0F)
lateinit var centerLocation: Location
var preview = false
var previewDistance = 0.0
val previewRouteData = MutableLiveData("")
lateinit var mapView: ComposeView
var panView = false
var tilt = 55.0
var previewDistance = 0.0
var countDownTimerActive = false
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
@@ -341,6 +333,18 @@ class SurfaceRenderer(
previewDistance = routeModel.route.distance
}
fun setLocation(location: Location) {
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = 0.0,
zoom = 15.0,
tilt = 0.0,
padding = PaddingValues(start = 350.dp, bottom = 0.dp),
target = Position(location.longitude, location.latitude)
)
)
}
companion
object {
private const val TAG = "MapRenderer"

View File

@@ -166,7 +166,7 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
Canvas(modifier =Modifier
.size(imageSize.dp, imageSize.dp)) {
scale(scaleX = 1f, scaleY = 0.7f) {
drawCircle(Color.DarkGray.copy(alpha = 0.4f))
drawCircle(Color.DarkGray.copy(alpha = 0.2f))
}
}
Icon(

View File

@@ -0,0 +1,69 @@
package com.kouros.navigation.car.screen
import android.location.Location
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants
class CategoriesScreen(
private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer,
private val location: Location,
) : Screen(carContext) {
var categories: List<Category> = listOf(
Category(id = Constants.GAS_STATION, name = carContext.getString(R.string.gas_station)),
Category(id = Constants.PHARMACY, name = carContext.getString(R.string.pharmacy)),
Category(id = Constants.CHARGING_STATION, name = carContext.getString(R.string.charging_station))
)
override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder()
.setNoItemsMessage("No categories to show")
categories.forEach {
it.name
itemListBuilder.addItem(
Row.Builder()
.setTitle(it.name)
.setOnClickListener {
screenManager
.pushForResult(
CategoryScreen(
carContext,
surfaceRenderer,
location,
it.id
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
}
.setBrowsable(true)
.build()
)
}
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle("title")
.build()
return ListTemplate.Builder()
.setHeader(header)
.setSingleList(itemListBuilder.build())
.build()
}
}

View File

@@ -0,0 +1,143 @@
package com.kouros.navigation.car.screen
import android.location.Location
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
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row
import androidx.car.app.model.Template
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.data.NavigationRepository
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.location
import kotlin.math.min
class CategoryScreen(
private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer,
location: Location,
category: String,
) : Screen(carContext) {
val viewModel = ViewModel(NavigationRepository())
var elements = listOf<Elements>()
val observer = Observer<List<Elements>> { newElements ->
elements = newElements
invalidate()
}
init {
viewModel.elements.observe(this, observer)
viewModel.getAmenities(category, location)
invalidate()
}
override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder()
if (carContext.getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
val listLimit = min(
10,
carContext.getCarService(ConstraintManager::class.java)
.getContentLimit(
ConstraintManager.CONTENT_LIMIT_TYPE_LIST
)
)
elements.forEach {
//listBuilder.addItem(createRow(it.tags!!.operator.toString(), it.tags?.capacity.toString()))
listBuilder.addItem(
Row.Builder()
.setOnClickListener({
val location = location(it.lon!!, it.lat!!)
surfaceRenderer.setLocation(location)
})
.setTitle(secondText(it.tags?.capacity.toString()))
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
com.kouros.android.cars.carappservice.R.drawable.ev_station_24px
)
)
.build()
)
.addText(it.tags!!.operator.toString())
.addText(secondText(it.tags?.capacity.toString()))
.build()
)
}
}
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.charging_station))
.build()
val actionStrip = ActionStrip.Builder()
.addAction(
Action.Builder()
.setOnClickListener {
CarToast.makeText(
carContext,
carContext.getString(
R.string.notification_demo
),
CarToast.LENGTH_SHORT
)
.show()
}
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_place_white_24dp
)
).build()
)
.setFlags(Action.FLAG_IS_PERSISTENT)
.build()
)
.build()
val builder = MapWithContentTemplate.Builder()
.setContentTemplate(
ListTemplate.Builder()
.setHeader(header)
.setSingleList(listBuilder.build())
.build()
)
.setActionStrip(actionStrip)
return builder.build()
}
private fun secondText(sText: String): CarText {
val secondText =
CarText.Builder(
"================= " + sText + " ================"
)
.addVariant(
("--------------------- " + sText
+ " ----------------------")
)
.addVariant(sText)
.build()
return secondText
}
}

View File

@@ -22,6 +22,7 @@ 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.categories
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel
@@ -152,6 +153,7 @@ class PlaceListScreen(
R.string.recent_Item_deleted, CarToast.LENGTH_LONG
).show()
loadPlaces()
invalidate()
}
.build()

View File

@@ -26,6 +26,7 @@ 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.Action.FLAG_PRIMARY
import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
@@ -94,6 +95,7 @@ class RoutePreviewScreen(
)
).build()
val navigateAction = Action.Builder()
.setFlags(FLAG_PRIMARY)
.setIcon(navigateActionIcon)
.setOnClickListener { this.onNavigate() }
.build()
@@ -108,58 +110,10 @@ class RoutePreviewScreen(
.setStartHeaderAction(Action.BACK)
.setTitle(carContext.getString(R.string.route_preview))
.addEndHeaderAction(
Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
if (mIsFavorite)
R.drawable.ic_favorite_filled_white_24dp
else
R.drawable.ic_favorite_white_24dp
)
)
.build()
)
.setOnClickListener {
mIsFavorite = !mIsFavorite
CarToast.makeText(
carContext,
if (mIsFavorite)
carContext
.getString(R.string.favorites)
else
carContext.getString(
R.string.not_favorite_toast_msg
),
CarToast.LENGTH_SHORT
)
.show()
vieModel.saveFavorite(destination)
println(destination)
invalidate()
}
.build()
favoriteAction()
)
.addEndHeaderAction(
Action.Builder()
.setOnClickListener {
if (mIsFavorite) {
vieModel.deleteFavorite(destination)
}
mIsFavorite = !mIsFavorite
finish()
}
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_delete_foreground
)
)
.build()
)
.build()
deleteFavoriteAction()
)
.build()
@@ -186,6 +140,56 @@ class RoutePreviewScreen(
.build()
}
private fun favoriteAction(): Action = Action.Builder()
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
if (mIsFavorite)
R.drawable.ic_favorite_filled_white_24dp
else
R.drawable.ic_favorite_white_24dp
)
)
.build()
)
.setOnClickListener {
mIsFavorite = !mIsFavorite
CarToast.makeText(
carContext,
if (mIsFavorite)
carContext
.getString(R.string.favorites)
else
carContext.getString(
R.string.not_favorite_toast_msg
),
CarToast.LENGTH_SHORT
)
.show()
vieModel.saveFavorite(destination)
invalidate()
}
.build()
private fun deleteFavoriteAction(): Action = Action.Builder()
.setOnClickListener {
if (mIsFavorite) {
vieModel.deleteFavorite(destination)
}
mIsFavorite = !mIsFavorite
finish()
}
.setIcon(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.ic_delete_foreground
)
)
.build()
)
.build()
private fun createRow(index: Int, action: Action): Row {
val route: CarText = createRouteText(index)
return Row.Builder()

View File

@@ -34,7 +34,8 @@ 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))
)
@@ -66,20 +67,36 @@ class SearchScreen(
.setTitle(it.name)
.setImage(categoryIcon(it.id))
.setOnClickListener {
screenManager
.pushForResult(
PlaceListScreen(
carContext,
surfaceRenderer,
location,
it.id
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
if (it.id == Constants.CATEGORIES) {
screenManager
.pushForResult(
CategoriesScreen(
carContext,
surfaceRenderer,
location,
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
}
} else {
screenManager
.pushForResult(
PlaceListScreen(
carContext,
surfaceRenderer,
location,
it.id
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
}
}
.setBrowsable(true)
.build()

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="M340,760L440,600L380,600L380,480L280,640L340,640L340,760ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM240,760L480,760L480,480L240,480L240,760ZM160,840L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L610,480Q639,480 659.5,500.5Q680,521 680,550L680,735Q680,752 694,766Q708,780 725,780Q743,780 756.5,766Q770,752 770,735L770,360L760,360Q743,360 731.5,348.5Q720,337 720,320L720,240L740,240L740,180L780,180L780,240L820,240L820,180L860,180L860,240L880,240L880,320Q880,337 868.5,348.5Q857,360 840,360L830,360L830,735Q830,777 799.5,808.5Q769,840 725,840Q682,840 651,808.5Q620,777 620,735L620,550Q620,545 617.5,542.5Q615,540 610,540L560,540L560,840L160,840ZM480,760L240,760L240,760L480,760Z"/>
</vector>

View File

@@ -57,8 +57,9 @@ dependencies {
annotationProcessor(libs.objectbox.processor)
implementation(libs.kotlinx.serialization.json)
implementation(libs.maplibre.compose)
implementation("hu.supercluster:overpasser:0.2.2")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View File

@@ -134,6 +134,13 @@ data class ValhallaLocation (
var language: String
)
data class BoundingBox (
var southernLat : Double,
var westernLon: Double,
var northerLat : Double,
var easternLon : Double
)
object Constants {
const val STYLE: String = "https://kouros-online.de/liberty.json"
@@ -141,12 +148,22 @@ object Constants {
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
const val TAG: String = "Navigation"
const val CATEGORIES: String = "Categories"
const val CONTACTS: String = "Contacts"
const val RECENT: String = "Recent"
const val FAVORITES: String = "Favorites"
const val GAS_STATION: String ="GasStation"
const val PHARMACY: String ="pharmacy"
const val CHARGING_STATION: String ="charging_station"
val categories = listOf("Tankstelle", "Apotheke", "Ladestationen")
/** The initial location to use as an anchor for searches. */
val homeLocation: Location = Location(LocationManager.GPS_PROVIDER)
val home2Location: Location = Location(LocationManager.GPS_PROVIDER)
@@ -176,7 +193,7 @@ object Constants {
const val MAXIMAL_ROUTE_DEVIATION = 100.0
const val DESTINATION_ARRIVAL_DISTANCE = 20.0
const val DESTINATION_ARRIVAL_DISTANCE = 40.0
}

View File

@@ -17,8 +17,8 @@
package com.kouros.navigation.data
import android.location.Location
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox
import org.json.JSONArray
import java.net.Authenticator
import java.net.HttpURLConnection
@@ -98,7 +98,6 @@ class NavigationRepository {
return places
}
private fun fetchUrl(url: String, authenticator : Boolean): String {
try {
if (authenticator) {

View File

@@ -0,0 +1,72 @@
package com.kouros.navigation.data
import android.location.Location
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.overpass.Amenity
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox2
import com.kouros.navigation.utils.NavigationUtils.getOverpassBbox
import hu.supercluster.overpasser.library.output.OutputFormat
import hu.supercluster.overpasser.library.output.OutputModificator
import hu.supercluster.overpasser.library.output.OutputOrder
import hu.supercluster.overpasser.library.output.OutputVerbosity
import hu.supercluster.overpasser.library.query.OverpassQuery
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
class Overpass {
fun getAmenities(category: String, location: Location) : List<Elements> {
val boundingBox = getOverpassBbox(location, 2.0)
val bb = getBoundingBox2(location, 2.0)
val url = "https://overpass.kumi.systems/api/interpreter"
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(
"Accept",
"application/json"
)
httpURLConnection.setDoOutput(true);
val query = OverpassQuery()
.format(OutputFormat.JSON)
.timeout(30)
.filterQuery()
.node()
.amenity("charging_station")
.tagNot("access", "private")
.boundingBox(bb.southernLat, bb.westernLon, bb.northerLat, bb.easternLon)
.end()
.output(OutputVerbosity.BODY, OutputModificator.CENTER, OutputOrder.QT, 100)
.build()
// define a query
val test = """
|[out:json];
|(
| node[amenity=$category]
| ($boundingBox);
|);
|out body;
""".trimMargin()
// Send the JSON we created
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(test)
outputStreamWriter.flush()
// Check if the connection is successful
val responseCode = httpURLConnection.responseCode
println("Overpass: $responseCode")
if (responseCode == HttpURLConnection.HTTP_OK) {
val response = httpURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
val gson = GsonBuilder().serializeNulls().create()
val overpass = gson.fromJson(response, Amenity::class.java)
println("Overpass: $response")
return overpass.elements
}
return emptyList()
}
}

View File

@@ -0,0 +1,13 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Amenity (
@SerializedName("version" ) var version : Double? = null,
@SerializedName("generator" ) var generator : String? = null,
@SerializedName("osm3s" ) var osm3s : Osm3s? = Osm3s(),
@SerializedName("elements" ) var elements : ArrayList<Elements> = arrayListOf()
)

View File

@@ -0,0 +1,14 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Elements (
@SerializedName("type" ) var type : String? = null,
@SerializedName("id" ) var id : Long? = null,
@SerializedName("lat" ) var lat : Double? = null,
@SerializedName("lon" ) var lon : Double? = null,
@SerializedName("tags" ) var tags : Tags? = Tags()
)

View File

@@ -0,0 +1,11 @@
package com.kouros.navigation.data.overpass
import com.google.gson.annotations.SerializedName
data class Osm3s (
@SerializedName("timestamp_osm_base" ) var timestampOsmBase : String? = null,
@SerializedName("copyright" ) var copyright : String? = null
)

View File

@@ -0,0 +1,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 socketType2Ooutput : String? = null
)

View File

@@ -22,7 +22,7 @@
"id": "background",
"type": "background",
"layout": {"visibility": "visible"},
"paint": {"background-color": "rgba(28, 28, 35, 1)"}
"paint": {"background-color": "rgba(39, 46, 57, 1)"}
},
{
"id": "natural_earth",
@@ -903,7 +903,11 @@
["match", ["get", "brunnel"], ["bridge", "tunnel"], false, true],
["match", ["get", "class"], ["service", "track"], true, false]
],
"layout": {"line-cap": "round", "line-join": "round"},
"layout": {
"line-cap": "round",
"line-join": "round",
"visibility": "visible"
},
"paint": {
"line-width": [
"interpolate",
@@ -916,7 +920,7 @@
20,
11
],
"line-color": "rgba(65, 74, 92, 1)"
"line-color": "rgba(77, 94, 123, 1)"
}
},
{
@@ -1005,7 +1009,7 @@
],
"layout": {"line-cap": "round", "line-join": "round"},
"paint": {
"line-color": "#e9ac77",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1029,7 +1033,7 @@
],
"layout": {"line-join": "round"},
"paint": {
"line-color": "#e9ac77",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1059,7 +1063,7 @@
],
"layout": {"line-cap": "round", "line-join": "round"},
"paint": {
"line-color": "#e9ac77",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1245,7 +1249,7 @@
],
"layout": {"line-cap": "round", "line-join": "round"},
"paint": {
"line-color": "rgba(105, 170, 87, 1)",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1271,7 +1275,7 @@
],
"layout": {"line-join": "round"},
"paint": {
"line-color": "#fea",
"line-color": "rgba(65, 74, 92, 1)",
"line-width": [
"interpolate",
["exponential", 1.2],
@@ -1306,7 +1310,7 @@
5,
"hsl(26,87%,62%)",
6,
"#fc8"
"#ab9"
],
"line-width": [
"interpolate",
@@ -1847,7 +1851,7 @@
],
"layout": {"line-join": "round"},
"paint": {
"line-color": "#fea",
"line-color": "rgba(65, 74, 92, 34)",
"line-width": [
"interpolate",
["exponential", 1.2],

View File

@@ -4,6 +4,8 @@ import android.location.Location
import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step
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.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType
import com.kouros.navigation.data.Place
@@ -35,6 +37,7 @@ open class RouteModel() {
set(value) {
routeState = routeState.copy(route = value)
}
fun startNavigation(routeString: String) {
val newRoute = Route.Builder()
.route(routeString)
@@ -49,7 +52,7 @@ open class RouteModel() {
this.routeState = routeState.copy(
route = null,
isNavigating = false,
// destination = Place(),
// destination = Place(),
arrived = false,
maneuverType = 0,
currentShapeIndex = 0,
@@ -100,30 +103,31 @@ open class RouteModel() {
}
fun currentStep(): StepData {
val currentManeuver = route.currentManeuver()
// Determine if we should display the current or the next maneuver
val distanceToNextStep = leftStepDistance()
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
val shouldAdvance =
isNearNextManeuver && route.currentManeuverIndex < (route.maneuvers.size - 1)
isNearNextManeuver && route.currentManeuverIndex < (route.maneuvers.size)
// Determine the maneuver type and corresponding icon
var maneuverType = if (hasArrived(currentManeuver.type)) {
currentManeuver.type
} else {
ManeuverType.None.value
}
// Get the single, correct maneuver for this state
val relevantManeuver = if (shouldAdvance) {
route.nextManeuver() // This advances the route's state
} else {
route.currentManeuver()
}
// Safely get the street name from the maneuver
val streetName = relevantManeuver.streetNames?.firstOrNull() ?: ""
// Determine the maneuver type and corresponding icon
val maneuverType = if (hasArrived(relevantManeuver.type)) {
relevantManeuver.type
} else {
ManeuverType.None.value
if (shouldAdvance) {
maneuverType = relevantManeuver.type
}
val maneuverIconPair = maneuverIcon(maneuverType)
// Construct and return the final StepData object
return StepData(
streetName,
@@ -135,6 +139,40 @@ 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
@@ -290,12 +328,10 @@ open class RouteModel() {
return routeState.isNavigating
}
fun isArrived(): Boolean {
return routeState.arrived
}
fun hasArrived(type: Int): Boolean {
return type == ManeuverType.DestinationRight.value
// return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value
return type == ManeuverType.DestinationRight.value
|| routeState.maneuverType == ManeuverType.Destination.value
|| routeState.maneuverType == ManeuverType.DestinationLeft.value
}

View File

@@ -13,11 +13,13 @@ 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
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.utils.NavigationUtils
import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor
@@ -55,6 +57,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
MutableLiveData<List<Place>>()
}
val elements: MutableLiveData<List<Elements>> by lazy {
MutableLiveData<List<Elements>>()
}
fun loadRecentPlace(location: Location) {
viewModelScope.launch(Dispatchers.IO) {
@@ -113,6 +118,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
.orderDesc(Place_.lastDate)
.build()
val results = query.find()
println("Favorites $results")
query.close()
for (place in results) {
val plLocation = location(place.longitude, place.latitude)
@@ -231,6 +237,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
return place.address.road
}
fun getAmenities(category: String, location: Location) {
viewModelScope.launch(Dispatchers.IO) {
val amenities = Overpass().getAmenities(category, location)
elements.postValue(amenities)
}
}
fun saveFavorite(place: Place) {
place.category = Constants.FAVORITES
savePlace(place)
@@ -258,7 +271,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
val current = LocalDateTime.now(ZoneOffset.UTC)
place.lastDate = current.atZone(ZoneOffset.UTC).toEpochSecond()
placeBox.put(place)
println("Save Recent $place")
println("Save Place $place")
} catch (e: Exception) {
e.printStackTrace()
}

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.BoundingBox
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection
@@ -158,6 +159,23 @@ object NavigationUtils {
return jsonString
}
fun getOverpassBbox(location: Location, radius: Double): String {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return "$swLon,$swLat,$neLon,$neLat"
}
fun getBoundingBox2(location: Location, radius: Double): BoundingBox {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return BoundingBox(swLat ?: 0.0 , swLon ?: 0.0, neLat ?: 0.0, neLon ?: 0.0)
}
fun getBoundingBox(
lat: Double,
lon: Double,
@@ -176,30 +194,6 @@ object NavigationUtils {
"se" to mapOf("lat" to minLat, "lon" to maxLon)
)
}
fun computeOffset(from: Location, distance: Double, heading: Double): Location {
val earthRadius = 6371009.0
var distance = distance
var heading = heading
distance /= earthRadius
heading = toRadians(heading)
val fromLat: Double = toRadians(from.latitude)
val fromLng: Double = toRadians(from.longitude)
val cosDistance: Double = cos(distance)
val sinDistance = sin(distance)
val sinFromLat = sin(fromLat)
val cosFromLat: Double = cos(fromLat)
val sinLat: Double = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading)
val dLng: Double = atan2(
sinDistance * cosFromLat * sin(heading),
cosDistance - sinFromLat * sinLat
)
val snap = Location(LocationManager.GPS_PROVIDER)
snap.latitude = toDegrees(asin(sinLat))
snap.longitude = toDegrees(fromLng + dLng)
return snap
//return LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng))
}
}
fun calculateZoom(speed: Double?): Double {
@@ -208,11 +202,12 @@ fun calculateZoom(speed: Double?): Double {
}
val speedKmh = (speed * 3.6).toInt()
val zoom = when (speedKmh) {
in 0..10 -> 17.0
in 11..30 -> 16.0
in 31..50 -> 16.0
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
else -> 15
else -> 14
}
return zoom.toDouble()
}