Stopover and Legs

This commit is contained in:
Dimitris
2026-03-31 17:06:55 +02:00
parent 60b842d883
commit 6838ad09c4
35 changed files with 542 additions and 280 deletions
@@ -2,11 +2,11 @@ package com.kouros.navigation.data
import androidx.compose.ui.graphics.Color
val NavigationColorLight = Color(0xFF066462)
val NavigationColorLight = Color(0xFF17A119)
val NavigationColorDark = Color(0xFF10DED9)
val NavigationColorDark = Color(0xFF4EDE10)
val RouteColor = Color(0xFF7B06E1)
val RouteColor = Color(0xFF05692D)
val SpeedColor = Color(0xFF262525)
@@ -46,6 +46,7 @@ data class Place(
var lastDate: Long = 0,
var routeIndex: Int = 0,
var route: String = "",
var stopOver: Boolean = false,
)
data class ContactData(
@@ -20,7 +20,7 @@ abstract class NavigationRepository {
abstract fun getRoute(
context: Context,
currentLocation: Location,
destination: Location,
location: List<Location>,
carOrientation: Float,
searchFilter: SearchFilter
): String
@@ -58,6 +58,8 @@ class DataStoreManager(private val context: Context) {
val ENGINE_TYPE = intPreferencesKey("EngineType")
val ALTERNATIVE_ROUTES = booleanPreferencesKey("AlternativeRoutes")
}
// Read values
@@ -145,6 +147,11 @@ class DataStoreManager(private val context: Context) {
?: EngineType.COMBUSTION.ordinal
}
val alternativeRoutesFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] == true
}
// Save values
suspend fun setShow3D(enabled: Boolean) {
context.dataStore.edit { preferences ->
@@ -235,4 +242,11 @@ class DataStoreManager(private val context: Context) {
prefs[PreferencesKeys.ENGINE_TYPE] = mode
}
}
suspend fun setAlternativeRoutes(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] = enabled
}
}
}
@@ -13,7 +13,7 @@ class OsrmRepository : NavigationRepository() {
override fun getRoute(
context: Context,
currentLocation: Location,
location: Location,
location: List<Location>,
carOrientation: Float,
searchFilter: SearchFilter
): String {
@@ -28,7 +28,7 @@ class OsrmRepository : NavigationRepository() {
if (searchFilter.avoidFerry) {
exclude = "$exclude&exclude=ferry"
}
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=false"
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.first().longitude},${location.first().latitude}?steps=true&alternatives=false"
return fetchUrl(routeUrl + routeLocation + exclude, true)
}
@@ -69,7 +69,7 @@ class OsrmRoute {
steps.add(step)
stepIndex += 1
}
legs.add(Leg(steps))
legs.add(Leg(steps, summary))
}
val routeGeoJson = createLineStringCollection(waypoints)
val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
@@ -1,5 +1,9 @@
package com.kouros.navigation.data.route
import android.location.Location
import com.kouros.navigation.utils.location
data class Leg(
var steps : List<Step> = arrayListOf(),
val summary: Summary,
)
@@ -13,3 +13,57 @@ data class Maneuver(
val message: String = "",
val pointIndex : Int = 0,
)
enum class ManeuverType {
TYPE_UNKNOWN,
TYPE_DEPART,
TYPE_NAME_CHANGE,
TYPE_KEEP_LEFT,
TYPE_KEEP_RIGHT,
TYPE_TURN_SLIGHT_LEFT,
TYPE_TURN_SLIGHT_RIGHT,
TYPE_TURN_NORMAL_LEFT,
TYPE_TURN_NORMAL_RIGHT,
TYPE_TURN_SHARP_LEFT,
TYPE_TURN_SHARP_RIGHT,
TYPE_U_TURN_LEFT,
TYPE_U_TURN_RIGHT,
TYPE_ON_RAMP_SLIGHT_LEFT,
TYPE_ON_RAMP_SLIGHT_RIGHT,
TYPE_ON_RAMP_NORMAL_LEFT,
TYPE_ON_RAMP_NORMAL_RIGHT,
TYPE_ON_RAMP_SHARP_LEFT,
TYPE_ON_RAMP_SHARP_RIGHT,
TYPE_ON_RAMP_U_TURN_LEFT,
TYPE_ON_RAMP_U_TURN_RIGHT,
TYPE_OFF_RAMP_SLIGHT_LEFT,
TYPE_OFF_RAMP_SLIGHT_RIGHT,
TYPE_OFF_RAMP_NORMAL_LEFT,
TYPE_OFF_RAMP_NORMAL_RIGHT,
TYPE_FORK_LEFT,
TYPE_FORK_RIGHT,
TYPE_MERGE_LEFT,
TYPE_MERGE_RIGHT,
TYPE_MERGE_SIDE_UNSPECIFIED,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE,
TYPE_STRAIGHT,
TYPE_FERRY_BOAT,
TYPE_FERRY_TRAIN,
TYPE_DESTINATION,
TYPE_DESTINATION_STRAIGHT,
TYPE_DESTINATION_LEFT,
TYPE_DESTINATION_RIGHT,
TYPE_ROUNDABOUT_ENTER_CW,
TYPE_ROUNDABOUT_EXIT_CW,
TYPE_ROUNDABOUT_ENTER_CCW,
TYPE_ROUNDABOUT_EXIT_CCW,
TYPE_FERRY_BOAT_LEFT,
TYPE_FERRY_BOAT_RIGHT,
TYPE_FERRY_TRAIN_LEFT,
TYPE_FERRY_TRAIN_RIGHT,
TYPE_WAYPOINT_RIGHT,
TYPE_WAYPOINT_LEFT
}
@@ -23,14 +23,14 @@ private const val tomtomFields =
val useLocal = BuildConfig.DEBUG
val useLocalTraffic = BuildConfig.DEBUG
val useLocalTraffic = BuildConfig.DEBUG
class TomTomRepository : NavigationRepository() {
override fun getRoute(
context: Context,
currentLocation: Location,
location: Location,
location: List<Location>,
carOrientation: Float,
searchFilter: SearchFilter
): String {
@@ -55,17 +55,34 @@ class TomTomRepository : NavigationRepository() {
engineType = "electric"
}
val repository = getSettingsRepository(context)
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() }
val tomtomApiKey = runBlocking {
repository.tomTomApiKeyFlow.first()
}
val alternativeRoutes = runBlocking {
repository.alternativeRoutesFlow.first()
}
val altRoutes = if (alternativeRoutes) {
"&maxAlternatives=2"
} else {
"&maxAlternatives=0"
}
val currentLocale = Locale.getDefault()
val language = currentLocale.language + "-" + currentLocale.country
var loc = ""
location.forEach {
loc += if (loc.isEmpty()) {
"${it.latitude},${it.longitude}"
} else {
":${it.latitude},${it.longitude}"
}
}
val url =
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" +
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:$loc" +
"/json?sectionType=traffic&report=effectiveSettings&routeType=eco" +
"&traffic=true&avoid=unpavedRoads&travelMode=car" +
"&vehicleMaxSpeed=120&vehicleCommercial=false" +
"&instructionsType=text&language=$language&sectionType=lanes" +
"&routeRepresentation=encodedPolyline" +
"&maxAlternatives=2" +
"&routeRepresentation=encodedPolyline$altRoutes" +
"&vehicleEngineType=$engineType$filter&key=$tomtomApiKey"
return fetchUrl(
url,
@@ -1,10 +1,13 @@
package com.kouros.navigation.data.tomtom
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.route.Intersection
import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.ManeuverType
import com.kouros.navigation.data.route.Routes
import com.kouros.navigation.data.route.Step
import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
@@ -17,12 +20,12 @@ import com.kouros.navigation.data.route.Maneuver as RouteManeuver
class TomTomRoute {
fun mapToRoute(routeJson: TomTomResponse, builder: Route.Builder) {
val routes = mutableListOf<com.kouros.navigation.data.route.Routes>()
val routes = mutableListOf<Routes>()
routeJson.routes.forEach { route ->
val waypoints = mutableListOf<List<Double>>()
val points = mutableListOf<List<Double>>()
val legs = mutableListOf<Leg>()
var stepIndex = 0
var points = listOf<List<Double>>()
val summary = Summary(
route.summary.travelTimeInSeconds.toDouble(),
route.summary.lengthInMeters.toDouble(),
@@ -30,89 +33,98 @@ class TomTomRoute {
route.summary.trafficLengthInMeters.toDouble()
)
route.legs.forEach { leg ->
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
waypoints.addAll(points)
val p = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
points.addAll(p)
waypoints.addAll(p)
}
var stepDistance = 0.0
var stepDuration = 0.0
val steps = mutableListOf<Step>()
var lastPointIndex = 0
for (index in 1..<route.guidance.instructions.size) {
val lastInstruction = route.guidance.instructions[index - 1]
val instruction = route.guidance.instructions[index]
val street = lastInstruction.street ?: ""
val maneuverStreet = instruction.street ?: ""
val maneuver = RouteManeuver(
bearingBefore = 0,
bearingAfter = 0,
type = convertType(instruction.maneuver),
waypoints = points.subList(
lastPointIndex,
instruction.pointIndex + 1,
),
exit = exitNumber(instruction),
location = location(
instruction.point.longitude, instruction.point.latitude
),
street = maneuverStreet,
message = instruction.message,
pointIndex = instruction.pointIndex
route.legs.forEach { leg ->
var stepDistance = 0.0
var stepDuration = 0.0
val steps = mutableListOf<Step>()
val summary = Summary(
leg.summary.travelTimeInSeconds.toDouble(),
leg.summary.lengthInMeters.toDouble(),
leg.summary.trafficDelayInSeconds.toDouble(),
leg.summary.trafficLengthInMeters.toDouble()
)
var lastPointIndex = 0
for (index in 1..<route.guidance.instructions.size) {
val lastInstruction = route.guidance.instructions[index - 1]
val instruction = route.guidance.instructions[index]
val street = lastInstruction.street ?: ""
val maneuverStreet = instruction.street ?: ""
val maneuver = RouteManeuver(
bearingBefore = 0,
bearingAfter = 0,
type = convertType(instruction.maneuver),
waypoints = points.subList(
lastPointIndex,
instruction.pointIndex + 1,
),
exit = exitNumber(instruction),
location = location(
instruction.point.longitude, instruction.point.latitude
),
street = maneuverStreet,
message = instruction.message,
pointIndex = instruction.pointIndex
)
lastPointIndex = instruction.pointIndex
val intersections = mutableListOf<Intersection>()
route.sections?.forEach { section ->
if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex
&& section.endPointIndex >= lastPointIndex
) {
val lanes = mutableListOf<Lane>()
var startIndex = 0
var lastLane: Lane? = null
section.lanes?.forEach { itLane ->
val lane = Lane(
location = location(
waypoints[section.startPointIndex][0],
waypoints[section.startPointIndex][1]
),
valid = itLane.directions.first() == itLane.follow,
indications = itLane.directions,
startIndex = startIndex,
endIndex = section.endPointIndex
)
startIndex = section.startPointIndex
if (lastLane == null
|| (!(lastLane.valid && lane.valid
&& lastLane.indications == lane.indications))
) {
lanes.add(lane)
lastPointIndex = instruction.pointIndex
val intersections = mutableListOf<Intersection>()
route.sections?.forEach { section ->
if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex
&& section.endPointIndex >= lastPointIndex
) {
val lanes = mutableListOf<Lane>()
var startIndex = 0
var lastLane: Lane? = null
section.lanes?.forEach { itLane ->
val lane = Lane(
location = location(
waypoints[section.startPointIndex][0],
waypoints[section.startPointIndex][1]
),
valid = itLane.directions.first() == itLane.follow,
indications = itLane.directions,
startIndex = startIndex,
endIndex = section.endPointIndex
)
startIndex = section.startPointIndex
if (lastLane == null
|| (!(lastLane.valid && lane.valid
&& lastLane.indications == lane.indications))
) {
lanes.add(lane)
}
lastLane = lane
}
lastLane = lane
intersections.add(Intersection(waypoints[startIndex], lanes))
}
intersections.add(Intersection(waypoints[startIndex], lanes))
}
stepDistance =
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration =
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val step = Step(
index = stepIndex,
street = street,
distance = stepDistance,
duration = stepDuration,
maneuver = maneuver,
intersection = intersections,
countryCode = lastInstruction.countryCode
)
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
steps.add(step)
stepIndex += 1
}
stepDistance =
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration =
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val step = Step(
index = stepIndex,
street = street,
distance = stepDistance,
duration = stepDuration,
maneuver = maneuver,
intersection = intersections,
countryCode = lastInstruction.countryCode
)
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
steps.add(step)
stepIndex += 1
legs.add(Leg(steps, summary))
}
legs.add(Leg(steps))
val routeGeoJson = createLineStringCollection(waypoints)
val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
val newRoute = com.kouros.navigation.data.route.Routes(
val newRoute = Routes(
legs,
summary,
routeGeoJson,
@@ -130,75 +142,79 @@ class TomTomRoute {
var newType = 0
when (type) {
"DEPART" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DEPART
newType = ManeuverType.TYPE_DEPART.ordinal
}
"ARRIVE" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION
newType = ManeuverType.TYPE_DESTINATION.ordinal
}
"ARRIVE_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_LEFT
newType = ManeuverType.TYPE_DESTINATION_LEFT.ordinal
}
"ARRIVE_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_RIGHT
newType = ManeuverType.TYPE_DESTINATION_RIGHT.ordinal
}
"STRAIGHT", "FOLLOW" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
newType = ManeuverType.TYPE_STRAIGHT.ordinal
}
"KEEP_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_RIGHT
newType = ManeuverType.TYPE_KEEP_RIGHT.ordinal
}
"BEAR_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
newType = ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal
}
"BEAR_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
newType = ManeuverType.TYPE_TURN_SLIGHT_LEFT.ordinal
}
"KEEP_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_LEFT
newType = ManeuverType.TYPE_KEEP_LEFT.ordinal
}
"TURN_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
newType = ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal
}
"TURN_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
newType = ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal
}
"SHARP_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_LEFT
newType = ManeuverType.TYPE_TURN_SHARP_LEFT.ordinal
}
"SHARP_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT
newType = ManeuverType.TYPE_TURN_SHARP_RIGHT.ordinal
}
"ROUNDABOUT_RIGHT", "ROUNDABOUT_CROSS" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
newType = ManeuverType.TYPE_ROUNDABOUT_ENTER_CCW.ordinal
}
"ROUNDABOUT_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW
newType = ManeuverType.TYPE_ROUNDABOUT_ENTER_CW.ordinal
}
"MAKE_UTURN" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_LEFT
"MAKE_UTURN", "TRY_MAKE_UTURN" -> {
newType = ManeuverType.TYPE_U_TURN_LEFT.ordinal
}
"ENTER_MOTORWAY" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_LEFT
newType = ManeuverType.TYPE_MERGE_LEFT.ordinal
}
"TAKE_EXIT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
newType = ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal
}
"WAYPOINT_RIGHT" -> {
newType = ManeuverType.TYPE_WAYPOINT_RIGHT.ordinal
}
}
return newType
@@ -16,7 +16,7 @@ class ValhallaRepository : NavigationRepository() {
override fun getRoute(
context: Context,
currentLocation: Location,
location: Location,
location: List<Location>,
carOrientation: Float,
searchFilter: SearchFilter
): String {
@@ -35,7 +35,7 @@ class ValhallaRepository : NavigationRepository() {
lon = currentLocation.longitude,
searchFilter = exclude
),
Locations(lat = location.latitude, lon = location.longitude, searchFilter = exclude)
Locations(lat = location.first().latitude, lon = location.first().longitude, searchFilter = exclude)
)
val valhallaLocation = ValhallaLocation(
locations = vLocation,
@@ -6,15 +6,12 @@ import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import androidx.annotation.DrawableRes
import androidx.car.app.model.CarIcon
import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.StepData
import java.util.Collections
import com.kouros.navigation.data.route.ManeuverType
import java.util.Locale
class IconMapper {
@@ -22,62 +19,66 @@ class IconMapper {
fun maneuverIcon(routeManeuverType: Int): Int {
var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) {
Maneuver.TYPE_STRAIGHT -> {
ManeuverType.TYPE_STRAIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_DESTINATION,
Maneuver.TYPE_DESTINATION_RIGHT,
Maneuver.TYPE_DESTINATION_LEFT,
Maneuver.TYPE_DESTINATION_STRAIGHT
ManeuverType.TYPE_DESTINATION.ordinal,
ManeuverType.TYPE_DESTINATION_RIGHT.ordinal,
ManeuverType.TYPE_DESTINATION_LEFT.ordinal,
ManeuverType.TYPE_DESTINATION_STRAIGHT.ordinal
-> {
currentTurnIcon = R.drawable.ic_turn_destination
}
Maneuver.TYPE_TURN_NORMAL_RIGHT -> {
ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_normal_right
}
Maneuver.TYPE_TURN_NORMAL_LEFT -> {
ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_normal_left
}
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> {
ManeuverType.TYPE_OFF_RAMP_SLIGHT_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> {
ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_slight_right
}
Maneuver.TYPE_KEEP_RIGHT -> {
ManeuverType.TYPE_KEEP_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_KEEP_LEFT -> {
ManeuverType.TYPE_KEEP_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_name_change
}
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> {
ManeuverType.TYPE_ROUNDABOUT_ENTER_CCW.ordinal -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> {
ManeuverType.TYPE_ROUNDABOUT_EXIT_CCW.ordinal -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
Maneuver.TYPE_U_TURN_LEFT -> {
ManeuverType.TYPE_U_TURN_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_u_turn_left
}
Maneuver.TYPE_U_TURN_RIGHT -> {
ManeuverType.TYPE_U_TURN_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_u_turn_right
}
Maneuver.TYPE_MERGE_LEFT -> {
ManeuverType.TYPE_MERGE_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_merge_symmetrical
}
ManeuverType.TYPE_WAYPOINT_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_destination
}
}
return currentTurnIcon
}
@@ -86,8 +87,8 @@ class IconMapper {
val laneDirection = when (direction.lowercase(Locale.getDefault())) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> LaneDirection.SHAPE_NORMAL_LEFT
ManeuverType.TYPE_STRAIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -95,7 +96,7 @@ class IconMapper {
"left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> LaneDirection.SHAPE_NORMAL_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -103,9 +104,9 @@ class IconMapper {
"straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_STRAIGHT
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_STRAIGHT
ManeuverType.TYPE_STRAIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
ManeuverType.TYPE_KEEP_LEFT.ordinal -> LaneDirection.SHAPE_STRAIGHT
ManeuverType.TYPE_KEEP_RIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -113,7 +114,7 @@ class IconMapper {
"right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> LaneDirection.SHAPE_NORMAL_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -121,8 +122,8 @@ class IconMapper {
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> LaneDirection.SHAPE_NORMAL_RIGHT
ManeuverType.TYPE_STRAIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -130,8 +131,8 @@ class IconMapper {
"left_slight", "slight_left" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> LaneDirection.SHAPE_SLIGHT_LEFT
ManeuverType.TYPE_KEEP_LEFT.ordinal -> LaneDirection.SHAPE_SLIGHT_LEFT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -139,8 +140,8 @@ class IconMapper {
"right_slight", "slight_right" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_SLIGHT_RIGHT
ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal -> LaneDirection.SHAPE_NORMAL_RIGHT
ManeuverType.TYPE_KEEP_RIGHT.ordinal -> LaneDirection.SHAPE_SLIGHT_RIGHT
else
-> LaneDirection.SHAPE_UNKNOWN
}
@@ -212,8 +213,8 @@ class IconMapper {
return when (direction) {
"left_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> "left_o_straight_x"
Maneuver.TYPE_STRAIGHT -> "left_x_straight_o"
ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> "left_o_straight_x"
ManeuverType.TYPE_STRAIGHT.ordinal -> "left_x_straight_o"
else
-> "left_x_straight_x"
}
@@ -221,29 +222,29 @@ class IconMapper {
"right_straight" -> {
when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> "right_x_straight_x"
Maneuver.TYPE_STRAIGHT -> "right_x_straight_o"
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> "right_o_straight_o"
ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> "right_x_straight_x"
ManeuverType.TYPE_STRAIGHT.ordinal -> "right_x_straight_o"
ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal -> "right_o_straight_o"
else
-> "right_x_straight_x"
}
}
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x"
"left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x"
"straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_LEFT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_RIGHT
"right" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal) "${direction}_o" else "${direction}_x"
"left" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal) "${direction}_o" else "${direction}_x"
"straight" -> if (stepData.currentManeuverType == ManeuverType.TYPE_STRAIGHT.ordinal
|| stepData.currentManeuverType == ManeuverType.TYPE_KEEP_LEFT.ordinal
|| stepData.currentManeuverType == ManeuverType.TYPE_KEEP_RIGHT.ordinal
) "${direction}_o" else "${direction}_x"
"right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT
|| stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_RIGHT
"right_slight", "slight_right" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal
|| stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal
|| stepData.currentManeuverType == ManeuverType.TYPE_KEEP_RIGHT.ordinal
) "slight_right_o" else "slight_right_x"
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT
|| stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_LEFT
"left_slight", "slight_left" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_SLIGHT_LEFT.ordinal
|| stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal
|| stepData.currentManeuverType == ManeuverType.TYPE_KEEP_LEFT.ordinal
) "slight_left_o" else "slight_left_x"
else -> {
@@ -3,6 +3,7 @@ package com.kouros.navigation.model
//import com.kouros.navigation.data.Preferences.boxStore
import android.content.Context
import android.location.Location
import android.util.Log
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.MutableLiveData
@@ -147,7 +148,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
fun loadRoute(
context: Context,
currentLocation: Location,
destination: Location,
destination: List<Location>,
carOrientation: Float
) {
viewModelScope.launch(Dispatchers.IO) {
@@ -181,9 +182,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
)
if (data.isNotEmpty()) {
val trafficData = rebuildTraffic(data)
traffic.postValue(
trafficData
)
if (trafficData.isNotEmpty()) {
traffic.postValue(
trafficData
)
}
}
} catch (e: Exception) {
e.printStackTrace()
@@ -234,7 +237,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
repository.getRoute(
context,
currentLocation,
location,
listOf(location),
carOrientation,
getSearchFilter(context)
)
@@ -102,6 +102,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
0
)
val alternativeRoutes = repository.alternativeRoutesFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
false
)
fun onShow3DChanged(enabled: Boolean) {
viewModelScope.launch { repository.setShow3D(enabled) }
}
@@ -159,4 +165,7 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
viewModelScope.launch { repository.setEngineType(mode) }
}
fun onAlternativeRoutes(enabled: Boolean) {
viewModelScope.launch { repository.setAlternativeRoutes(enabled) }
}
}
@@ -50,6 +50,9 @@ class SettingsRepository(
val engineTypeFlow: Flow<Int> =
dataStoreManager.engineTypeFlow
val alternativeRoutesFlow: Flow<Boolean> =
dataStoreManager.alternativeRoutesFlow
suspend fun setShow3D(enabled: Boolean) {
dataStoreManager.setShow3D(enabled)
}
@@ -109,4 +112,8 @@ class SettingsRepository(
suspend fun setEngineType(mode: Int) {
dataStoreManager.setEngineType(mode)
}
suspend fun setAlternativeRoutes(enabled: Boolean) {
dataStoreManager.setAlternativeRoutes(enabled)
}
}
@@ -71,4 +71,5 @@
<string name="combustion">Combustion</string>
<string name="electric">Electric</string>
<string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources>
@@ -55,4 +55,5 @@
<string name="combustion">Combustion</string>
<string name="electric">Electric</string>
<string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources>
@@ -55,4 +55,5 @@
<string name="combustion">Combustion</string>
<string name="electric">Electric</string>
<string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources>
@@ -58,4 +58,5 @@
<string name="combustion">Combustion</string>
<string name="electric">Electric</string>
<string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources>
@@ -55,8 +55,8 @@ class RouteCalculatorTest {
)
}
private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0): Route {
val leg = Leg(steps = steps)
private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0, summary: Summary): Route {
val leg = Leg(steps = steps, summary)
val routes = Routes(
legs = listOf(leg),
summary = Summary(),
@@ -74,7 +74,7 @@ class RouteCalculatorTest {
fun `findStep updates currentStepIndex to step containing the nearest waypoint`() {
val step0 = createStep(index = 0, numWaypoints = 2)
val step1 = createStep(index = 1, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1), summary = Summary()))
val mockLocation: Location = mock()
// step0/wp0: 500F, step0/wp1: 400F, step1/wp0: 300F, step1/wp1: 8F
@@ -88,7 +88,7 @@ class RouteCalculatorTest {
@Test
fun `findStep updates waypointIndex to the nearest waypoint within the step`() {
val step0 = createStep(index = 0, numWaypoints = 3)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = Summary()))
val mockLocation: Location = mock()
// wp0: 100F, wp1: 30F (nearest), wp2: 80F
@@ -104,7 +104,7 @@ class RouteCalculatorTest {
val step0 = createStep(index = 0, numWaypoints = 2)
val step1 = createStep(index = 1, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy(
route = setupRoute(listOf(step0, step1), currentStepIndex = 1)
route = setupRoute(listOf(step0, step1), currentStepIndex = 1, summary = Summary())
)
val mockLocation: Location = mock()
@@ -123,7 +123,7 @@ class RouteCalculatorTest {
val step1 = createStep(index = 1, numWaypoints = 2)
val step2 = createStep(index = 2, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy(
route = setupRoute(listOf(step0, step1, step2))
route = setupRoute(listOf(step0, step1, step2), summary = Summary())
)
val mockLocation: Location = mock()
@@ -147,7 +147,7 @@ class RouteCalculatorTest {
val step0 = createStep(index = 0, numWaypoints = 2, duration = 60.0, waypointIndex = 0)
val step1 = createStep(index = 1, numWaypoints = 2, duration = 120.0)
val step2 = createStep(index = 2, numWaypoints = 2, duration = 90.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2), summary = Summary()))
val result = routeCalculator.travelLeftTime()
@@ -160,7 +160,7 @@ class RouteCalculatorTest {
// waypointIndex=2, waypoints=4 → percent = 100*(4-2)/4 = 50 → time = 80*50/100 = 40s
val step0 = createStep(index = 0, numWaypoints = 4, duration = 80.0, waypointIndex = 2)
val step1 = createStep(index = 1, numWaypoints = 2, duration = 40.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1), summary = Summary()))
val result = routeCalculator.travelLeftTime()
@@ -172,7 +172,7 @@ class RouteCalculatorTest {
fun `travelLeftTime returns only future steps when at last step`() {
val step0 = createStep(index = 0, numWaypoints = 2, duration = 60.0, waypointIndex = 1)
routeModel.navState = routeModel.navState.copy(
route = setupRoute(listOf(step0), currentStepIndex = 0)
route = setupRoute(listOf(step0), currentStepIndex = 0, summary = Summary())
)
val result = routeCalculator.travelLeftTime()
@@ -189,7 +189,7 @@ class RouteCalculatorTest {
fun `leftStepDistance returns 0 when waypointIndex is at the last position`() {
// Loop range: waypointIndex..<waypoints.size-1 = 2..<2, which is empty
val step0 = createStep(index = 0, numWaypoints = 3, waypointIndex = 2)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = Summary()))
val result = routeCalculator.leftStepDistance()
@@ -206,7 +206,7 @@ class RouteCalculatorTest {
val step0 = createStep(index = 0, numWaypoints = 2, distance = 100.0, waypointIndex = 1)
val step1 = createStep(index = 1, numWaypoints = 2, distance = 200.0)
val step2 = createStep(index = 2, numWaypoints = 2, distance = 150.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2), summary = Summary()))
val result = routeCalculator.travelLeftDistance()
@@ -217,7 +217,7 @@ class RouteCalculatorTest {
@Test
fun `travelLeftDistance returns 0 when on last step at last waypoint`() {
val step0 = createStep(index = 0, numWaypoints = 2, distance = 200.0, waypointIndex = 1)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0)))
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = Summary()))
val result = routeCalculator.travelLeftDistance()
@@ -232,7 +232,8 @@ class RouteCalculatorTest {
fun `arrivalTime returns a timestamp roughly travelLeftTime seconds in the future`() {
// step0: 2 waypoints at wp0 → 100% of 3600s duration
val step0 = createStep(index = 0, numWaypoints = 2, duration = 3600.0, waypointIndex = 0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0)))
val summary = Summary(duration = 3600.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = summary))
val before = System.currentTimeMillis()
val result = routeCalculator.arrivalTime()
@@ -48,7 +48,7 @@ class RouteModelTest {
}
private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0): Route {
val leg = Leg(steps = steps)
val leg = Leg(steps = steps, summary = Summary())
val routes = Routes(
legs = listOf(leg),
summary = Summary(),