From d546ede0e57eef72aa14f66bca421a6a40af5bb5 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Tue, 16 Dec 2025 07:19:29 +0100 Subject: [PATCH] Overpass --- app/build.gradle.kts | 4 +- .../com/kouros/navigation/ui/MainActivity.kt | 10 +- .../kouros/navigation/car/SurfaceRenderer.kt | 26 ++-- .../com/kouros/navigation/car/map/MapView.kt | 2 +- .../navigation/car/screen/CategoriesScreen.kt | 69 +++++++++ .../navigation/car/screen/CategoryScreen.kt | 143 ++++++++++++++++++ .../navigation/car/screen/PlaceListScreen.kt | 2 + .../car/screen/RoutePreviewScreen.kt | 104 +++++++------ .../navigation/car/screen/SearchScreen.kt | 45 ++++-- .../src/main/res/drawable/ev_station_24px.xml | 10 ++ common/data/build.gradle.kts | 3 +- .../java/com/kouros/navigation/data/Data.kt | 19 ++- .../navigation/data/NavigationRepository.kt | 3 +- .../com/kouros/navigation/data/Overpass.kt | 72 +++++++++ .../navigation/data/overpass/Amenity.kt | 13 ++ .../navigation/data/overpass/Elements.kt | 14 ++ .../kouros/navigation/data/overpass/Osm3s.kt | 11 ++ .../kouros/navigation/data/overpass/Tags.kt | 22 +++ .../navigation/data/styles/liberty_night.json | 24 +-- .../com/kouros/navigation/model/RouteModel.kt | 64 ++++++-- .../com/kouros/navigation/model/ViewModel.kt | 15 +- .../navigation/utils/NavigationUtils.kt | 51 +++---- 22 files changed, 589 insertions(+), 137 deletions(-) create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt create mode 100644 common/car/src/main/res/drawable/ev_station_24px.xml create mode 100644 common/data/src/main/java/com/kouros/navigation/data/Overpass.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/data/overpass/Amenity.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/data/overpass/Osm3s.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index da2732c..0df094a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt index 5f6692b..428e323 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -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()) { diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index f3ba79d..ea77adc 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -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" diff --git a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt index d361cfb..c1a156c 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt @@ -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( diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt new file mode 100644 index 0000000..0f4a5ef --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt @@ -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 = 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() + } +} \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt new file mode 100644 index 0000000..7f5225d --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt @@ -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() + + val observer = Observer> { 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 + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt index b0f76eb..7d29022 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt @@ -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() diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 1985c7d..9d6a6fe 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -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() diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt index 185168b..e5b4ad9 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt @@ -34,7 +34,8 @@ class SearchScreen( var categories: List = 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() diff --git a/common/car/src/main/res/drawable/ev_station_24px.xml b/common/car/src/main/res/drawable/ev_station_24px.xml new file mode 100644 index 0000000..4d6ec4c --- /dev/null +++ b/common/car/src/main/res/drawable/ev_station_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/build.gradle.kts b/common/data/build.gradle.kts index a23b530..420b673 100644 --- a/common/data/build.gradle.kts +++ b/common/data/build.gradle.kts @@ -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) diff --git a/common/data/src/main/java/com/kouros/navigation/data/Data.kt b/common/data/src/main/java/com/kouros/navigation/data/Data.kt index 990687b..c7cddee 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Data.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Data.kt @@ -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 } diff --git a/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt index 99da2e9..97a434e 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt @@ -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) { diff --git a/common/data/src/main/java/com/kouros/navigation/data/Overpass.kt b/common/data/src/main/java/com/kouros/navigation/data/Overpass.kt new file mode 100644 index 0000000..e99fd73 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/Overpass.kt @@ -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 { + 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() + } +} \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Amenity.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Amenity.kt new file mode 100644 index 0000000..725a984 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Amenity.kt @@ -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 = arrayListOf() + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt new file mode 100644 index 0000000..7b3dad2 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt @@ -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() + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Osm3s.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Osm3s.kt new file mode 100644 index 0000000..dfdf061 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Osm3s.kt @@ -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 + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt new file mode 100644 index 0000000..93c3f4b --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt @@ -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 + +) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/styles/liberty_night.json b/common/data/src/main/java/com/kouros/navigation/data/styles/liberty_night.json index 18263a5..2250e59 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/styles/liberty_night.json +++ b/common/data/src/main/java/com/kouros/navigation/data/styles/liberty_night.json @@ -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], diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index 704f699..04e1e27 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -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) = 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 } diff --git a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt index 0fa8188..b00c212 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt @@ -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>() } + val elements: MutableLiveData> by lazy { + MutableLiveData>() + } 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() } diff --git a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt index 3680f24..83f0ad0 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt @@ -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() }