Overpass
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
10
common/car/src/main/res/drawable/ev_station_24px.xml
Normal file
10
common/car/src/main/res/drawable/ev_station_24px.xml
Normal 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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
)
|
||||
@@ -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()
|
||||
|
||||
)
|
||||
@@ -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
|
||||
|
||||
)
|
||||
@@ -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
|
||||
|
||||
)
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user