Compare commits

...

5 Commits

Author SHA1 Message Date
Dimitris
1b8abbd4eb Refactoring Route, Speed 2025-12-24 09:55:53 +01:00
Dimitris
d0a07e1315 Umbau 2025-12-24 07:24:12 +01:00
Dimitris
ddae6f2189 Icons 2025-12-23 17:26:11 +01:00
Dimitris
01a4eb7fca Icons 2025-12-23 17:24:11 +01:00
Dimitris
8673380b9e Icons and strings 2025-12-23 16:22:24 +01:00
63 changed files with 741 additions and 311 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -41,12 +41,9 @@ import com.google.android.gms.location.LocationServices
import com.kouros.navigation.MainApplication.Companion.navigationViewModel import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.MockLocation import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.ui.theme.NavigationTheme import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
@@ -114,13 +111,16 @@ class MainActivity : ComponentActivity() {
} }
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationClient.lastLocation
.addOnSuccessListener { location : android.location.Location? ->
if (useMock) { if (useMock) {
mock = MockLocation(locationManager) mock = MockLocation(locationManager)
mock.setMockLocation( mock.setMockLocation(
homeLocation.latitude, location?.latitude ?: homeLocation.latitude,
homeLocation.longitude location?.longitude ?: homeLocation.longitude
) )
} }
}
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
CheckPermissionScreen() CheckPermissionScreen()
@@ -165,11 +165,6 @@ class MainActivity : ComponentActivity() {
} }
val step: StepData? by stepData.observeAsState() val step: StepData? by stepData.observeAsState()
val nextStep: StepData? by nextStepData.observeAsState() val nextStep: StepData? by nextStepData.observeAsState()
fun openSheet() {
scope.launch { scaffoldState.bottomSheetState.expand() }
}
fun closeSheet() { fun closeSheet() {
scope.launch { scope.launch {
scaffoldState.bottomSheetState.partialExpand() scaffoldState.bottomSheetState.partialExpand()
@@ -236,13 +231,13 @@ class MainActivity : ComponentActivity() {
if (isNavigating()) { if (isNavigating()) {
updateLocation(currentLocation, navigationViewModel) updateLocation(currentLocation, navigationViewModel)
stepData.value = currentStep() stepData.value = currentStep()
if (route.currentManeuverIndex + 1 <= route.maneuvers.size) { if (route.currentStep + 1 <= legs.steps.size) {
nextStepData.value = nextStep() nextStepData.value = nextStep()
} }
if (routeState.maneuverType == 39 if (routeState.maneuverType == 39
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) { ) {
stopNavigation() // stopNavigation()
routeState = routeState.copy(arrived = true) routeState = routeState.copy(arrived = true)
routeData.value = "" routeData.value = ""
} }
@@ -302,20 +297,21 @@ class MainActivity : ComponentActivity() {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun simulate() = GlobalScope.async { fun simulate() = GlobalScope.async {
for ((_, loc) in routeModel.route.waypoints.withIndex()) { for ((index, step) in routeModel.legs.steps.withIndex()) {
if (routeModel.isNavigating()) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
mock.setMockLocation(loc[1], loc[0]) mock.setMockLocation(waypoint[1], waypoint[0])
delay(500L) // delay(600L) //
} }
} }
} }
fun test() { fun test() {
for ((index, loc) in routeModel.route.waypoints.withIndex()) { for ((index, step) in routeModel.legs.steps.withIndex()) {
if (index > 300) { println("${step.maneuver.waypoints.size}")
routeModel.updateLocation(location(loc[0], loc[1]), navigationViewModel) for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation(location(waypoint[0], waypoint[1]), navigationViewModel)
routeModel.currentStep() routeModel.currentStep()
if (routeModel.route.currentManeuverIndex + 1 <= routeModel.route.maneuvers.size) { if (index + 1 <= routeModel.legs.steps.size) {
nextStepData.value = routeModel.nextStep() nextStepData.value = routeModel.nextStep()
} }
println(routeModel.routeState.maneuverType) println(routeModel.routeState.maneuverType)

View File

@@ -55,7 +55,7 @@ fun NavigationSheet(
simulateNavigation() simulateNavigation()
}) { }) {
Icon( Icon(
painter = painterResource(id = R.drawable.assistant_navigation_48px), painter = painterResource(id = R.drawable.navigation_48px),
"Simulate", "Simulate",
modifier = Modifier.size(24.dp, 24.dp), modifier = Modifier.size(24.dp, 24.dp),
) )

View File

@@ -151,7 +151,7 @@ fun SearchBar(
SearchBarDefaults.InputField( SearchBarDefaults.InputField(
leadingIcon = { leadingIcon = {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_search_black36dp), painter = painterResource(id = R.drawable.search_48px),
"Search", "Search",
modifier = Modifier.size(24.dp, 24.dp), modifier = Modifier.size(24.dp, 24.dp),
) )

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<automotiveApp>
<uses name="template" />
</automotiveApp>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Navigation</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<automotiveApp>
<uses name="template" />
</automotiveApp>

View File

@@ -0,0 +1,18 @@
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<paths>
<files-path name="res" path="/res"/>
</paths>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -300,7 +300,7 @@ class SurfaceRenderer(
with(routeModel) { with(routeModel) {
routeData.value = route.routeGeoJson routeData.value = route.routeGeoJson
centerLocation = route.centerLocation centerLocation = route.centerLocation
previewDistance = route.distance previewDistance = route.summary!!.distance
} }
updateCameraPosition( updateCameraPosition(
0.0, 0.0,

View File

@@ -1,5 +1,6 @@
package com.kouros.navigation.car.map package com.kouros.navigation.car.map
import android.annotation.SuppressLint
import android.location.Location import android.location.Location
import android.content.Context import android.content.Context
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
@@ -15,6 +16,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.scale
@@ -164,6 +166,7 @@ fun AmenityLayer(routeData: String?) {
) )
} }
} }
@Composable @Composable
fun BuildingLayer(tiles: Source) { fun BuildingLayer(tiles: Source) {
Anchor.Replace("building-3d") { Anchor.Replace("building-3d") {
@@ -177,7 +180,13 @@ fun BuildingLayer(tiles: Source) {
} }
@Composable @Composable
fun DrawNavigationImages(padding: PaddingValues, speed: Float?, maxSpeed: Int, width: Int, height: Int) { fun DrawNavigationImages(
padding: PaddingValues,
speed: Float?,
maxSpeed: Int,
width: Int,
height: Int
) {
NavigationImage(padding, width, height) NavigationImage(padding, width, height)
CurrentSpeed(width, height, speed) CurrentSpeed(width, height, speed)
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) { if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
@@ -187,7 +196,7 @@ fun DrawNavigationImages(padding: PaddingValues, speed: Float?, maxSpeed: Int, w
@Composable @Composable
fun NavigationImage(padding: PaddingValues, width: Int, height: Int) { fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
val imageSize = (height / 6) val imageSize = (height / 8)
val color = remember { NavigationColor } val color = remember { NavigationColor }
Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) { Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) {
Canvas( Canvas(
@@ -195,14 +204,15 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
.size(imageSize.dp, imageSize.dp) .size(imageSize.dp, imageSize.dp)
) { ) {
scale(scaleX = 1f, scaleY = 0.7f) { scale(scaleX = 1f, scaleY = 0.7f) {
drawCircle(Color.DarkGray.copy(alpha = 0.2f)) drawCircle(Color.DarkGray.copy(alpha = 0.3f))
} }
} }
Icon( Icon(
painter = painterResource(id = R.drawable.navigation), painter = painterResource(id = R.drawable.navigation_48px),
"Navigation", "Navigation",
tint = color.copy(alpha = 1f), tint = color.copy(alpha = 0.7f),
modifier = Modifier.size(imageSize.dp, imageSize.dp), modifier = Modifier.size(imageSize.dp, imageSize.dp)
.scale(scaleX = 1f, scaleY = 0.7f),
) )
} }
} }

View File

@@ -137,7 +137,7 @@ class RouteCarModel() : RouteModel() {
val dismissAction: Action = createToastAction( val dismissAction: Action = createToastAction(
carContext, carContext,
R.string.speed_camera, R.string.exit_action_title, R.string.exit_action_title, R.string.exit_action_title,
FLAG_DEFAULT FLAG_DEFAULT
) )

View File

@@ -34,7 +34,9 @@ import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlin.math.absoluteValue
class NavigationScreen( class NavigationScreen(
carContext: CarContext, carContext: CarContext,
@@ -294,7 +296,7 @@ class NavigationScreen(
CarIcon.Builder( CarIcon.Builder(
IconCompat.createWithResource( IconCompat.createWithResource(
carContext, carContext,
R.drawable.assistant_navigation_48px R.drawable.navigation_48px
) )
) )
.build() .build()
@@ -328,7 +330,7 @@ class NavigationScreen(
private fun searchAction(): Action { private fun searchAction(): Action {
return Action.Builder() return Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.ic_search_black36dp)) .setIcon(routeModel.createCarIcon(carContext, R.drawable.search_48px))
.setOnClickListener { .setOnClickListener {
startSearchScreen() startSearchScreen()
} }
@@ -337,7 +339,7 @@ class NavigationScreen(
private fun settingsAction(): Action { private fun settingsAction(): Action {
return Action.Builder() return Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_24px)) .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_applications_48px))
.setOnClickListener { .setOnClickListener {
screenManager.push(SettingsScreen(carContext)) screenManager.push(SettingsScreen(carContext))
} }
@@ -490,7 +492,12 @@ class NavigationScreen(
} }
val sortedList = updatedCameras.sortedWith(compareBy { it.distance }) val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
val camera = sortedList.first() val camera = sortedList.first()
if (camera.distance < 80) { val bearingSpeedCamera = location.bearingTo(location(camera.lon!!, camera.lat!!))
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
if (camera.distance < 80
&& (bearingSpeedCamera.absoluteValue - bearingRoute.absoluteValue).absoluteValue < 15.0
) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed) routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
} }
} }

View File

@@ -152,7 +152,7 @@ class PlaceListScreen(
.setIcon( .setIcon(
RouteCarModel().createCarIcon( RouteCarModel().createCarIcon(
carContext, carContext,
R.drawable.ic_delete_foreground R.drawable.ic_pan_24
) )
) )
.setOnClickListener { .setOnClickListener {

View File

@@ -89,7 +89,7 @@ class RoutePreviewScreen(
) )
.build() .build()
val message = if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) { val message = if (routeModel.isNavigating() && routeModel.route.waypoints!!.isNotEmpty()) {
createRouteText() createRouteText()
} else { } else {
CarText.Builder("Wait") CarText.Builder("Wait")
@@ -143,7 +143,7 @@ class RoutePreviewScreen(
.getString(R.string.favorites) .getString(R.string.favorites)
else else
carContext.getString( carContext.getString(
R.string.not_favorite_toast_msg R.string.favorites
), ),
CarToast.LENGTH_SHORT CarToast.LENGTH_SHORT
) )
@@ -165,7 +165,7 @@ class RoutePreviewScreen(
CarIcon.Builder( CarIcon.Builder(
IconCompat.createWithResource( IconCompat.createWithResource(
carContext, carContext,
R.drawable.ic_delete_foreground R.drawable.ic_pan_24
) )
) )
.build() .build()
@@ -173,8 +173,8 @@ class RoutePreviewScreen(
.build() .build()
private fun createRouteText(): CarText { private fun createRouteText(): CarText {
val time = routeModel.route.summary.time val time = routeModel.route.summary!!.duration
val length = BigDecimal(routeModel.route.distance).setScale(1, RoundingMode.HALF_EVEN) val length = BigDecimal(routeModel.route.summary!!.distance).setScale(1, RoundingMode.HALF_EVEN)
val firstRoute = SpannableString(" \u00b7 $length km") val firstRoute = SpannableString(" \u00b7 $length km")
firstRoute.setSpan( firstRoute.setSpan(
DurationSpan.create(time.toLong()), 0, 1, 0 DurationSpan.create(time.toLong()), 0, 1, 0

View File

@@ -137,7 +137,7 @@ class SearchScreen(
R.drawable.ic_favorite_white_24dp R.drawable.ic_favorite_white_24dp
} }
else -> { else -> {
R.drawable.navigation R.drawable.navigation_48px
} }
} }
return CarIcon.Builder( return CarIcon.Builder(

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M190,840L160,810L480,80L800,810L770,840L480,708L190,840Z"/>
</vector>

View File

@@ -179,3 +179,6 @@ object Constants {
} }
enum class RouteEngine {
VALHALLA, OSRM, GRAPHHOPPER
}

View File

@@ -40,7 +40,7 @@ abstract class NavigationRepository {
val route = getRoute(currentLocation, location, searchFilter) val route = getRoute(currentLocation, location, searchFilter)
val routeModel = RouteModel() val routeModel = RouteModel()
routeModel.startNavigation(route) routeModel.startNavigation(route)
return routeModel.route.distance return routeModel.route.summary!!.distance
} }
fun searchPlaces(search: String, location: Location) : String { fun searchPlaces(search: String, location: Location) : String {

View File

@@ -2,125 +2,97 @@ package com.kouros.navigation.data
import android.location.Location import android.location.Location
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.kouros.navigation.data.valhalla.Maneuvers import com.kouros.navigation.data.osrm.OsrmResponse
import com.kouros.navigation.data.valhalla.Summary import com.kouros.navigation.data.osrm.OsrmRoute
import com.kouros.navigation.data.valhalla.Trip import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.valhalla.ValhallaJson import com.kouros.navigation.data.route.Step
import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.data.valhalla.ValhallaResponse
import com.kouros.navigation.data.valhalla.ValhallaRoute
import com.kouros.navigation.utils.GeoUtils.createCenterLocation import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
import com.kouros.navigation.utils.GeoUtils.decodePolyline
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
import org.maplibre.geojson.Point import org.maplibre.geojson.Point
data class Route( data class Route(
/**
* A Leg is a route between only two waypoints.
*
* @since 1.0.0
*/
val maneuvers: List<Maneuvers>,
/**
* The distance traveled from origin to destination.
*
* @return a double number with unit meters
* @since 1.0.0
*/
val distance: Double,
/**
* List of [List<Double>] objects. Each `waypoint` is an input coordinate
* snapped to the road and path network. The `waypoint` appear in the list in the order of
* the input coordinates.
*
* @since 1.0.0
*/
val waypoints: List<List<Double>>,
/**
* List of [List<Point>] objects. Each `Point` is an input coordinate
* snapped to the road and path network. The `waypoint` appear in the list in the order of
* the input coordinates.
*
* @since 1.0.0
*/
val pointLocations: List<Point>,
val summary: Summary,
val trip: Trip,
val time: Double,
val routeGeoJson: String,
val currentManeuverIndex : Int,
val centerLocation: Location
val routeEngine: Int,
val summary: Summary?,
val legs: List<Leg>?,
val routeGeoJson: String = "",
val centerLocation : Location = location(0.0, 0.0),
var currentStep : Int = 0,
val waypoints: List<List<Double>>?,
) { ) {
class Builder { data class Builder (
private lateinit var maneuvers: List<Maneuvers>
private var distance: Double = 0.0
private var time: Double = 0.0
private lateinit var waypoints: List<List<Double>>
private lateinit var pointLocations: List<Point>
private lateinit var summary: Summary
private lateinit var trip: Trip
private var routeGeoJson = ""
private var centerLocation = location(0.0, 0.0)
var routeEngine : Int = RouteEngine.VALHALLA.ordinal,
var summary: Summary? = null,
var legs: List<Leg>? = null,
var routeGeoJson: String = "",
var centerLocation: Location = location(0.0, 0.0),
var waypoints : List<List<Double>>? = null,) {
fun routeType (routeEngine: Int) = apply {this.routeEngine = routeEngine }
fun summary(summary: Summary) = apply { this.summary = summary }
fun legs(legs: List<Leg>) = apply { this.legs = legs }
fun routeGeoJson(routeGeoJson: String) = apply {
this.routeGeoJson = routeGeoJson
centerLocation = createCenterLocation(routeGeoJson)
}
fun waypoints(waypoints: List<List<Double>>) = apply { this.waypoints = waypoints }
fun route(route: String) = apply { fun route(route: String) = apply {
if (route.isNotEmpty() && route != "[]") { if (route.isNotEmpty() && route != "[]") {
val gson = GsonBuilder().serializeNulls().create() val gson = GsonBuilder().serializeNulls().create()
val routeJson = gson.fromJson(route, ValhallaJson::class.java) if (routeEngine == RouteEngine.VALHALLA.ordinal) {
trip = routeJson.trip val jsonObject: Map<String, JsonElement> = Json.parseToJsonElement(route).jsonObject
val routeJson = gson.fromJson(jsonObject["trip"].toString(), ValhallaResponse::class.java)
ValhallaRoute().mapJsonToValhalla(routeJson, this)
} else {
val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
OsrmRoute().mapToOsrm(osrmJson, this)
}
} }
} }
fun build(): Route { fun build(): Route {
maneuvers = trip.legs[0].maneuvers
summary = trip.summary
distance = summary.length
time = summary.time
waypoints = decodePolyline(trip.legs[0].shape)
val points = mutableListOf<Point>()
for (loc in waypoints) {
val point = Point.fromLngLat(loc[0], loc[1])
points.add(point)
}
pointLocations = points
routeGeoJson = createLineStringCollection( waypoints)
centerLocation = createCenterLocation(routeGeoJson)
return Route( return Route(
maneuvers, routeEngine = this.routeEngine,
distance, summary = this.summary,
waypoints, legs = this.legs,
pointLocations, waypoints = this.waypoints,
summary, routeGeoJson = this.routeGeoJson,
trip,
time,
routeGeoJson,
0,
centerLocation
) )
} }
} }
fun maneuverLocations(): List<Point> { fun maneuverLocations(): List<Point> {
return pointLocations val step = currentStep()
val waypoints = step.maneuver.waypoints
val points = mutableListOf<Point>()
for (loc in waypoints) {
val point = Point.fromLngLat(loc[0], loc[1])
points.add(point)
}
return points
} }
fun currentManeuver(): Maneuvers { fun currentStep(): Step {
return maneuvers[currentManeuverIndex] if (legs != null) {
return legs.first().steps[currentStep]
} else {
throw IndexOutOfBoundsException("No legs available.")
}
} }
fun nextManeuver(): Maneuvers { fun nextStep(): Step {
val nextIndex = currentManeuverIndex + 1 val nextIndex = currentStep + 1
return if (nextIndex < maneuvers.size) { return if (nextIndex < legs!!.first().steps.size) {
maneuvers[nextIndex] legs.first().steps[currentStep + 1]
} else { } else {
throw IndexOutOfBoundsException("No next maneuver available.") throw IndexOutOfBoundsException("No next maneuver available.")
} }

View File

@@ -3,7 +3,7 @@ package com.kouros.navigation.data.osrm
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class OsrmJson ( data class OsrmResponse (
@SerializedName("code" ) var code : String? = null, @SerializedName("code" ) var code : String? = null,
@SerializedName("routes" ) var routes : ArrayList<Routes> = arrayListOf(), @SerializedName("routes" ) var routes : ArrayList<Routes> = arrayListOf(),

View File

@@ -0,0 +1,11 @@
package com.kouros.navigation.data.osrm
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.valhalla.ValhallaResponse
class OsrmRoute {
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
}
}

View File

@@ -28,6 +28,7 @@ class Overpass {
|); |);
|out body; |out body;
""".trimMargin() """.trimMargin()
println("way[highway](around:$radius,$linestring)")
return overpassApi(httpURLConnection, searchQuery) return overpassApi(httpURLConnection, searchQuery)
} }
@@ -60,7 +61,6 @@ class Overpass {
} }
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> { fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
// Send the JSON we created
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream) val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
outputStreamWriter.write(searchQuery) outputStreamWriter.write(searchQuery)
outputStreamWriter.flush() outputStreamWriter.flush()

View File

@@ -0,0 +1,5 @@
package com.kouros.navigation.data.route
data class Leg(
var steps : List<Step> = arrayListOf()
)

View File

@@ -0,0 +1,8 @@
package com.kouros.navigation.data.route
data class Maneuver(
val bearingBefore : Int = 0,
val bearingAfter : Int = 0,
val type: Int = 0,
val waypoints: List<List<Double>>,
)

View File

@@ -0,0 +1,10 @@
package com.kouros.navigation.data.route
class Step(
var index : Int = 0,
var waypointIndex : Int = 0,
val maneuver: Maneuver,
val duration: Double = 0.0,
val distance: Double = 0.0,
val name : String = "",
)

View File

@@ -0,0 +1,6 @@
package com.kouros.navigation.data.route
data class Summary(
var duration : Double = 0.0,
var distance : Double = 0.0,
)

View File

@@ -2,7 +2,6 @@ package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@@ -11,7 +10,7 @@ import kotlinx.serialization.json.JsonIgnoreUnknownKeys
data class Legs ( data class Legs (
@SerializedName("maneuvers" ) var maneuvers : ArrayList<Maneuvers> = arrayListOf(), @SerializedName("maneuvers" ) var maneuvers : ArrayList<Maneuvers> = arrayListOf(),
@SerializedName("summary" ) var summary : Summary = Summary(), @SerializedName("summary" ) var summaryValhalla : SummaryValhalla = SummaryValhalla(),
@SerializedName("shape" ) var shape : String = "" @SerializedName("shape" ) var shape : String = ""
) )

View File

@@ -2,13 +2,12 @@ package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys @JsonIgnoreUnknownKeys
data class Summary ( data class SummaryValhalla (
@SerializedName("has_time_restrictions" ) var hasTimeRestrictions : Boolean = false, @SerializedName("has_time_restrictions" ) var hasTimeRestrictions : Boolean = false,
@SerializedName("has_toll" ) var hasToll : Boolean = false, @SerializedName("has_toll" ) var hasToll : Boolean = false,

View File

@@ -1,21 +0,0 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class Trip (
@SerializedName("locations" ) var locations : ArrayList<Locations> = arrayListOf(),
@SerializedName("legs" ) var legs : ArrayList<Legs> = arrayListOf(),
@SerializedName("summary" ) var summary : Summary = Summary(),
@SerializedName("status_message" ) var statusMessage : String = "",
@SerializedName("status" ) var status : Int = 0,
@SerializedName("units" ) var units : String = "",
@SerializedName("language" ) var language : String = "",
)

View File

@@ -1,16 +0,0 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class ValhallaJson (
@SerializedName("trip" ) var trip : Trip = Trip(),
@SerializedName("id" ) var id : String = ""
)

View File

@@ -0,0 +1,20 @@
package com.kouros.navigation.data.valhalla
import com.google.gson.annotations.SerializedName
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
@OptIn(ExperimentalSerializationApi::class)
@JsonIgnoreUnknownKeys
data class ValhallaResponse(
@SerializedName("locations") var locations: ArrayList<Locations> = arrayListOf(),
@SerializedName("legs") var legs: ArrayList<Legs> = arrayListOf(),
@SerializedName("summary") var summaryValhalla: SummaryValhalla = SummaryValhalla(),
@SerializedName("status_message") var statusMessage: String = "",
@SerializedName("status") var status: Int = 0,
@SerializedName("units") var units: String = "",
@SerializedName("language") var language: String = "",
)

View File

@@ -0,0 +1,43 @@
package com.kouros.navigation.data.valhalla
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Maneuver
import com.kouros.navigation.data.route.Step
import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
import com.kouros.navigation.utils.GeoUtils.decodePolyline
class ValhallaRoute {
fun mapJsonToValhalla(routeJson: ValhallaResponse, builder: Route.Builder) {
val waypoints = decodePolyline(routeJson.legs[0].shape)
val summary = Summary()
summary.distance = routeJson.summaryValhalla.length
summary.duration = routeJson.summaryValhalla.time
val steps = mutableListOf<Step>()
var stepIndex = 0
routeJson.legs[0].maneuvers.forEach {
val maneuver = Maneuver(
bearingBefore = 0,
bearingAfter = it.bearingAfter,
type = it.type,
waypoints =waypoints.subList(it.beginShapeIndex, it.endShapeIndex+1)
)
var name = ""
if (it.streetNames != null && it.streetNames.isNotEmpty()) {
name = it.streetNames[0]
}
val step = Step( index = stepIndex, name = name, distance = it.length, duration = it.time, maneuver = maneuver)
steps.add(step)
stepIndex += 1
}
val leg = Leg(steps)
builder
.routeType(1)
.summary(summary)
.routeGeoJson(createLineStringCollection(waypoints))
.legs(listOf(leg))
.waypoints(waypoints)
}
}

View File

@@ -9,6 +9,7 @@ import com.kouros.navigation.data.ManeuverType
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -24,15 +25,10 @@ open class RouteModel() {
val destination: Place = Place(), val destination: Place = Place(),
val arrived: Boolean = false, val arrived: Boolean = false,
val maneuverType: Int = 0, val maneuverType: Int = 0,
var currentShapeIndex: Int = 0,
var distanceToStepEnd: Float = 0F,
var beginIndex: Int = 0,
var endIndex: Int = 0,
val travelMessage: String = "", val travelMessage: String = "",
// max speed for street (maneuver) val lastSpeedLocation: Location = location(0.0, 0.0),
val lastSpeedIndex: Int = 0, val lastSpeedIndex: Int = 0,
val maxSpeed: Int = 0, val maxSpeed: Int = 0,
) )
var routeState = RouteState() var routeState = RouteState()
@@ -43,6 +39,10 @@ open class RouteModel() {
routeState = routeState.copy(route = value) routeState = routeState.copy(route = value)
} }
val legs: Leg
get() = routeState.route!!.legs!!.first()
fun startNavigation(routeString: String) { fun startNavigation(routeString: String) {
val newRoute = Route.Builder() val newRoute = Route.Builder()
.route(routeString) .route(routeString)
@@ -59,40 +59,41 @@ open class RouteModel() {
isNavigating = false, isNavigating = false,
arrived = false, arrived = false,
maneuverType = 0, maneuverType = 0,
currentShapeIndex = 0,
distanceToStepEnd = 0F,
beginIndex = 0,
endIndex = 0
) )
} }
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun updateLocation(location: Location, viewModel: ViewModel) { fun updateLocation(location: Location, viewModel: ViewModel) {
var nearestDistance = 100000.0f findStep(location)
var newShapeIndex = -1
// find nearest waypoint and current shape index
// start search at last shape index
for (i in routeState.currentShapeIndex..<route.waypoints.size) {
val waypoint = route.waypoints[i]
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
if (distance < nearestDistance) {
nearestDistance = distance
newShapeIndex = i
}
}
// find maneuver
// calculate distance to step end
findManeuver(newShapeIndex)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
updateSpeedLimit(location, viewModel) updateSpeedLimit(location, viewModel)
} }
} }
private fun findStep(location: Location) {
var nearestDistance = 100000.0f
for ((index, step) in legs.steps.withIndex()) {
if (index >= route.currentStep && nearestDistance > 0) {
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
if (wayIndex >= step.waypointIndex) {
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
if (distance < nearestDistance) {
nearestDistance = distance
route.currentStep = step.index
step.waypointIndex = wayIndex
}
}
}
}
}
}
fun updateSpeedLimit(location: Location, viewModel: ViewModel) { fun updateSpeedLimit(location: Location, viewModel: ViewModel) {
// speed limit for each maneuver index // speed limit
if (routeState.lastSpeedIndex < route.currentManeuverIndex) { val distance = routeState.lastSpeedLocation.distanceTo(location)
routeState = routeState.copy(lastSpeedIndex = route.currentManeuverIndex) if (distance > 500 || routeState.lastSpeedIndex < route.currentStep) {
routeState = routeState.copy(lastSpeedIndex = route.currentStep)
routeState = routeState.copy(lastSpeedLocation = location)
val elements = viewModel.getMaxSpeed(location) val elements = viewModel.getMaxSpeed(location)
elements.forEach { elements.forEach {
if (it.tags.name != null && it.tags.maxspeed != null) { if (it.tags.name != null && it.tags.maxspeed != null) {
@@ -103,52 +104,30 @@ open class RouteModel() {
} }
} }
private fun findManeuver(newShapeIndex: Int) {
for (i in route.currentManeuverIndex..<route.maneuvers.size) {
val maneuver = route.maneuvers[i]
if (maneuver.beginShapeIndex <= newShapeIndex && maneuver.endShapeIndex >= newShapeIndex) {
route = route.copy(currentManeuverIndex = i)
routeState.apply {
currentShapeIndex = newShapeIndex
beginIndex = maneuver.beginShapeIndex
endIndex = maneuver.endShapeIndex
distanceToStepEnd = 0F
// calculate shape distance to step end
for (j in newShapeIndex + 1..maneuver.endShapeIndex) {
val loc1 = location(route!!.waypoints[j - 1][0], route.waypoints[j - 1][1])
val loc2 = location(route.waypoints[j][0], route.waypoints[j][1])
distanceToStepEnd += loc1.distanceTo(loc2)
}
break
}
}
}
}
fun currentStep(): StepData { fun currentStep(): StepData {
val currentManeuver = route.currentManeuver() val currentStep = route.currentStep()
// Determine if we should display the current or the next maneuver // Determine if we should display the current or the next maneuver
val distanceToNextStep = leftStepDistance() val distanceToNextStep = leftStepDistance()
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
val shouldAdvance = val shouldAdvance =
isNearNextManeuver && route.currentManeuverIndex < (route.maneuvers.size) isNearNextManeuver && route.currentStep < (route.legs!!.first().steps.size)
// Determine the maneuver type and corresponding icon // Determine the maneuver type and corresponding icon
var maneuverType = if (hasArrived(currentManeuver.type)) { var maneuverType = if (hasArrived(currentStep.maneuver.type)) {
currentManeuver.type currentStep.maneuver.type
} else { } else {
ManeuverType.None.value ManeuverType.None.value
} }
// Get the single, correct maneuver for this state // Get the single, correct maneuver for this state
val relevantManeuver = if (shouldAdvance) { val relevantManeuver = if (shouldAdvance) {
route.nextManeuver() // This advances the route's state route.nextStep() // This advances the route's state
} else { } else {
route.currentManeuver() route.currentStep()
} }
// Safely get the street name from the maneuver // Safely get the street name from the maneuver
val streetName = relevantManeuver.streetNames?.firstOrNull() ?: "" val streetName = relevantManeuver.name
if (shouldAdvance) { if (shouldAdvance) {
maneuverType = relevantManeuver.type maneuverType = relevantManeuver.maneuver.type
} }
val maneuverIconPair = maneuverIcon(maneuverType) val maneuverIconPair = maneuverIcon(maneuverType)
routeState = routeState.copy(maneuverType = maneuverIconPair.first) routeState = routeState.copy(maneuverType = maneuverIconPair.first)
@@ -165,8 +144,8 @@ open class RouteModel() {
fun nextStep(): StepData { fun nextStep(): StepData {
val maneuver = route.nextManeuver() val step = route.nextStep()
val maneuverType = maneuver.type val maneuverType = step.maneuver.type
val distanceLeft = leftStepDistance() val distanceLeft = leftStepDistance()
var text = "" var text = ""
@@ -175,8 +154,8 @@ open class RouteModel() {
} }
else -> { else -> {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { if (step.name.isNotEmpty()) {
text = maneuver.streetNames[0] text = step.name
} }
} }
} }
@@ -195,18 +174,16 @@ open class RouteModel() {
fun travelLeftTime(): Double { fun travelLeftTime(): Double {
var timeLeft = 0.0 var timeLeft = 0.0
for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) { for (i in route.currentStep + 1..<legs.steps.size) {
val maneuver = route.maneuvers[i] val step = legs.steps[i]
timeLeft += maneuver.time timeLeft += step.duration
} }
if (routeState.endIndex > 0) { val step = route.nextStep()
val maneuver = route.currentManeuver() val curTime = step.duration
val curTime = maneuver.time
val percent = val percent =
100 * (routeState.endIndex - routeState.currentShapeIndex) / (routeState.endIndex - routeState.beginIndex) 100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
val time = curTime * percent / 100 val time = curTime * percent / 100
timeLeft += time timeLeft += time
}
return timeLeft return timeLeft
} }
@@ -221,11 +198,11 @@ open class RouteModel() {
/** Returns the current [Step] left distance in m. */ /** Returns the current [Step] left distance in m. */
fun leftStepDistance(): Double { fun leftStepDistance(): Double {
val maneuver = route.currentManeuver() val step = route.currentStep()
var leftDistance = maneuver.length var leftDistance = step.distance
if (routeState.endIndex > 0) { val percent =
leftDistance = (routeState.distanceToStepEnd / 1000).toDouble() 100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
} leftDistance = leftDistance * percent / 100
// The remaining distance to the step, rounded to the nearest 10 units. // The remaining distance to the step, rounded to the nearest 10 units.
return (leftDistance * 1000 / 10.0).roundToInt() * 10.0 return (leftDistance * 1000 / 10.0).roundToInt() * 10.0
@@ -234,18 +211,17 @@ open class RouteModel() {
/** Returns the left distance in km. */ /** Returns the left distance in km. */
fun travelLeftDistance(): Double { fun travelLeftDistance(): Double {
var leftDistance = 0.0 var leftDistance = 0.0
for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) { for (i in route.currentStep + 1..<legs.steps.size) {
val maneuver = route.maneuvers[i] val step = route.legs!![0].steps[i]
leftDistance += maneuver.length leftDistance += step.distance
} }
if (routeState.endIndex > 0) {
val maneuver = route.currentManeuver() val step = route.currentStep()
val curDistance = maneuver.length val curDistance = step.distance
val percent = val percent =
100 * (routeState.endIndex - routeState.currentShapeIndex) / (routeState.endIndex - routeState.beginIndex) 100 * (step.maneuver.waypoints.size - step.waypointIndex) / (step.maneuver.waypoints.size)
val time = curDistance * percent / 100 val time = curDistance * percent / 100
leftDistance += time leftDistance += time
}
return leftDistance return leftDistance
} }
@@ -311,7 +287,6 @@ open class RouteModel() {
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
} }
//routeState.maneuverType = type
return Pair(type, currentTurnIcon) return Pair(type, currentTurnIcon)
} }
@@ -321,7 +296,6 @@ open class RouteModel() {
fun hasArrived(type: Int): Boolean { fun hasArrived(type: Int): Boolean {
// return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value
return type == ManeuverType.DestinationRight.value return type == ManeuverType.DestinationRight.value
|| type == ManeuverType.Destination.value || type == ManeuverType.Destination.value
|| type == ManeuverType.DestinationLeft.value || type == ManeuverType.DestinationLeft.value

View File

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

View File

@@ -0,0 +1,26 @@
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6.4,19 L5,17.6 10.6,12 5,6.4 6.4,5 12,10.6 17.6,5 19,6.4 13.4,12 19,17.6 17.6,19 12,13.4Z"/>
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,21 L10.55,19.7Q6.625,16.2 4.312,13.612Q2,11.025 2,8.15Q2,5.8 3.575,4.225Q5.15,2.65 7.5,2.65Q8.825,2.65 10,3.212Q11.175,3.775 12,4.75Q12.825,3.775 14,3.212Q15.175,2.65 16.5,2.65Q18.85,2.65 20.425,4.225Q22,5.8 22,8.15Q22,11.025 19.688,13.612Q17.375,16.2 13.45,19.7ZM12,18.3Q8.425,15.05 6.213,12.7Q4,10.35 4,8.15Q4,6.65 5,5.65Q6,4.65 7.5,4.65Q8.675,4.65 9.675,5.312Q10.675,5.975 11.05,7H12.95Q13.325,5.975 14.325,5.312Q15.325,4.65 16.5,4.65Q18,4.65 19,5.65Q20,6.65 20,8.15Q20,10.35 17.788,12.7Q15.575,15.05 12,18.3ZM12,18.3Q15.575,15.05 17.788,12.7Q20,10.35 20,8.15Q20,6.65 19,5.65Q18,4.65 16.5,4.65Q15.325,4.65 14.325,5.312Q13.325,5.975 12.95,7H11.05Q10.675,5.975 9.675,5.312Q8.675,4.65 7.5,4.65Q6,4.65 5,5.65Q4,6.65 4,8.15Q4,10.35 6.213,12.7Q8.425,15.05 12,18.3Z"/>
</vector>

View File

@@ -0,0 +1,26 @@
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,21 L10.55,19.7Q6.625,16.2 4.312,13.612Q2,11.025 2,8.15Q2,5.8 3.575,4.225Q5.15,2.65 7.5,2.65Q8.825,2.65 10,3.212Q11.175,3.775 12,4.75Q12.825,3.775 14,3.212Q15.175,2.65 16.5,2.65Q18.85,2.65 20.425,4.225Q22,5.8 22,8.15Q22,11.025 19.688,13.612Q17.375,16.2 13.45,19.7ZM12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475Q12,11.475 12,11.475ZM12,18.3Q15.575,15.05 17.788,12.7Q20,10.35 20,8.15Q20,6.65 19,5.65Q18,4.65 16.5,4.65Q15.325,4.65 14.325,5.312Q13.325,5.975 12.95,7H11.05Q10.675,5.975 9.675,5.312Q8.675,4.65 7.5,4.65Q6,4.65 5,5.65Q4,6.65 4,8.15Q4,10.35 6.213,12.7Q8.425,15.05 12,18.3Z"/>
</vector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M15.54,5.54L13.77,7.3 12,5.54 10.23,7.3 8.46,5.54 12,2zM18.46,15.54l-1.76,-1.77L18.46,12l-1.76,-1.77 1.76,-1.77L22,12zM8.46,18.46l1.77,-1.76L12,18.46l1.77,-1.76 1.77,1.76L12,22zM5.54,8.46l1.76,1.77L5.54,12l1.76,1.77 -1.76,1.77L2,12z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM18,10.2C18,6.57 15.35,4 12,4s-6,2.57 -6,6.2c0,2.34 1.95,5.44 6,9.14 4.05,-3.7 6,-6.8 6,-9.14zM12,2c4.2,0 8,3.22 8,8.2 0,3.32 -2.67,7.25 -8,11.8 -5.33,-4.55 -8,-8.48 -8,-11.8C4,5.22 7.8,2 12,2z"
android:fillColor="#FFFFFF"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

View File

@@ -0,0 +1,25 @@
<!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,13H5v-2h14v2z"/>
</vector>

View File

@@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal"> android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.63,468 649.31,482.69Q664,497.37 664,518L664,737Q664,758.68 679.5,773.34Q695,788 717,788Q739,788 754.5,773.34Q770,758.68 770,737L770,442Q759,448 747,451Q735,454 723,454Q683.52,454 656.26,426.74Q629,399.48 629,360Q629,328.39 647,303.19Q665,278 695,270L600,175L636,140L789,293Q803,307 811.5,323.5Q820,340 820,360L820,737Q820,780.26 790.18,810.13Q760.37,840 717.18,840Q674,840 644,810.13Q614,780.26 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM723,404Q741,404 754,391Q767,378 767,360Q767,342 754,329Q741,316 723,316Q705,316 692,329Q679,342 679,360Q679,378 692,391Q705,404 723,404Z"/> android:pathData="M160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.63,468 649.31,482.69Q664,497.37 664,518L664,737Q664,758.68 679.5,773.34Q695,788 717,788Q739,788 754.5,773.34Q770,758.68 770,737L770,442Q759,448 747,451Q735,454 723,454Q683.52,454 656.26,426.74Q629,399.48 629,360Q629,328.39 647,303.19Q665,278 695,270L600,175L636,140L789,293Q803,307 811.5,323.5Q820,340 820,360L820,737Q820,780.26 790.18,810.13Q760.37,840 717.18,840Q674,840 644,810.13Q614,780.26 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM723,404Q741,404 754,391Q767,378 767,360Q767,342 754,329Q741,316 723,316Q705,316 692,329Q679,342 679,360Q679,378 692,391Q705,404 723,404ZM220,780L489,780L489,468L220,468L220,780ZM489,780L220,780L220,780L489,780Z"/>
</vector> </vector>

View File

@@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal"> android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M120,840L120,780L207,525L120,270L120,210L647,210L709,40L777,67L725,210L840,210L840,270L752,525L840,780L840,840L120,840ZM452,679L512,679L512,555L636,555L636,495L512,495L512,371L452,371L452,495L328,495L328,555L452,555L452,679Z"/> android:pathData="M120,840L120,780L207,525L120,270L120,210L647,210L709,40L777,67L725,210L840,210L840,270L752,525L840,780L840,840L120,840ZM452,679L512,679L512,555L636,555L636,495L512,495L512,371L452,371L452,495L328,495L328,555L452,555L452,679ZM182,780L778,780L690,525L778,270L182,270L270,525L182,780ZM480,525L480,525L480,525L480,525L480,525L480,525Z"/>
</vector> </vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840L160,800L480,80L800,800L760,840L480,720L200,840ZM284,716L480,632L676,716L480,276L284,716ZM480,632L480,632L480,632L480,632Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M190,840L160,810L480,80L800,810L770,840L480,708L190,840ZM258,742L480,644L702,742L480,228L258,742ZM480,644L480,644L480,644L480,644Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M402,920Q375,920 350.5,907.5Q326,895 311,872L54,492L74,476Q91,461 113.5,457Q136,453 158.07,470.19L280,563L280,150Q280,137.25 288.68,128.62Q297.35,120 310.18,120Q323,120 331.5,128.62Q340,137.25 340,150L340,678L166,550L361,838Q368,849 378.5,854.5Q389,860 402,860L541,860L690,860Q728,860 754,834Q780,808 780,770L780,190Q780,177.25 788.68,168.62Q797.35,160 810.18,160Q823,160 831.5,168.62Q840,177.25 840,190L840,770Q840,833 796.5,876.5Q753,920 690,920L402,920ZM447,480L447,70Q447,57.25 455.68,48.62Q464.35,40 477.18,40Q490,40 498.5,48.62Q507,57.25 507,70L507,480L447,480ZM614,480L614,110Q614,97.25 622.68,88.62Q631.35,80 644.18,80Q657,80 665.5,88.62Q674,97.25 674,110L674,480L614,480ZM473,670Q473,670 473,670Q473,670 473,670L473,670L473,670L473,670L473,670L473,670Q473,670 473,670Q473,670 473,670Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M796,839L533,576Q503,602 463.04,616.5Q423.08,631 378,631Q269.84,631 194.92,556Q120,481 120,375Q120,269 195,194Q270,119 376.5,119Q483,119 557.5,194Q632,269 632,375.15Q632,418 618,458Q604,498 576,533L840,795L796,839ZM377,571Q458.25,571 515.13,513.5Q572,456 572,375Q572,294 515.13,236.5Q458.25,179 377,179Q294.92,179 237.46,236.5Q180,294 180,375Q180,456 237.46,513.5Q294.92,571 377,571Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M452,674L508,674L518,620Q538,614 552,605Q566,596 578,584L640,603L666,549L619,519Q623,498 623,480Q623,462 619,441L666,411L640,357L578,376Q566,364 552,355Q538,346 518,340L508,286L452,286L442,340Q422,346 408,355Q394,364 382,376L320,357L294,411L341,441Q337,462 337,480Q337,498 341,519L294,549L320,603L382,584Q394,596 408,605Q422,614 442,620L452,674ZM480,565Q444,565 419.5,540.5Q395,516 395,480Q395,444 419.5,419.5Q444,395 480,395Q516,395 540.5,419.5Q565,444 565,480Q565,516 540.5,540.5Q516,565 480,565ZM180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,180Q780,180 780,180Q780,180 780,180L180,180Q180,180 180,180Q180,180 180,180L180,780Q180,780 180,780Q180,780 180,780ZM180,180L180,180Q180,180 180,180Q180,180 180,180L180,780Q180,780 180,780Q180,780 180,780L180,780Q180,780 180,780Q180,780 180,780L180,180Q180,180 180,180Q180,180 180,180Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M715,592L647,553L772,460L840,499L715,592ZM518,493L648,397L333,219Q333,219 333,219Q333,219 333,219L258,346Q258,346 258,346Q258,346 258,346L518,493ZM453,356L453,356Q453,356 453,356Q453,356 453,356L453,356Q453,356 453,356Q453,356 453,356L453,356ZM160,800L160,740L370,740Q370,740 370,740Q370,740 370,740L370,478L228,398Q205.93,385.04 199.47,361.02Q193,337 206,315L281,190Q294,169 317.5,162.5Q341,156 362,168L757,391L522,564L430,512L430,740Q430,764.75 412.38,782.37Q394.75,800 370,800L160,800Z"/>
</vector>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<declare-styleable name="ShowcaseTheme">
<attr name="themedIconColor" format="color"/>
<attr name="markerIconTintColor" format="color"/>
<attr name="markerIconTintColorDark" format="color"/>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="speed_camera">Speed camera</string>
<string name="exit_action_title">Dismiss</string>
<string name="fuel_station">Gas station</string>
<string name="pharmacy">Pharmacy</string>
<string name="charging_station">Charging station</string>
<string name="category_title">Category</string>
<string name="on_action_title">On</string>
<string name="off_action_title">Off</string>
<string name="use_telephon_settings">Use telephon settings</string>
<string name="dark_mode">Dark mode</string>
<string name="display_settings">Display settings</string>
<string name="threed_building">3D building</string>
<string name="arrived_exclamation_msg">Arrived!</string>
<string name="drive_now">Drive now</string>
<string name="stop_action_title">Stop</string>
<string name="avoid_highways_row_title">Avoid highways</string>
<string name="avoid_tolls_row_title">Avoid tolls rows</string>
<string name="no_places">No places</string>
<string name="recent_destinations">Recent destination</string>
<string name="contacts">Contacts</string>
<string name="favorites">Favorites</string>
<string name="recent_Item_deleted">Recent item deleted</string>
<string name="route_preview">Route preview</string>
<string name="display">Display</string>
<string name="navigation_settings">Navigation settings</string>
<string name="settings_action_title">Settings</string>
<string name="accept_action_title">Accept</string>
<string name="reject_action_title">Reject</string>
<string name="ok_action_title">OK</string>
<string name="search_action_title">Search</string>
</resources>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<style name="CarAppTheme">
<item name="themedIconColor">#FFFF0000</item>
<item name="carColorPrimary">#ff7f39fb</item>
<item name="carColorPrimaryDark">#5904DF</item>
<item name="carColorSecondary">#328E10</item>
<item name="carColorSecondaryDark">#1A6004</item>
<item name="markerIconTintColor">#FF7F39FB</item>
<item name="markerIconTintColorDark">#FF5904DF</item>
</style>
</resources>