Refactoring

This commit is contained in:
Dimitris
2025-12-10 17:08:25 +01:00
parent aeca6ff237
commit a02673af36
92 changed files with 557 additions and 1643 deletions

View File

@@ -1,4 +1,5 @@
import org.gradle.kotlin.dsl.annotationProcessor
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
@@ -8,8 +9,6 @@ plugins {
kotlin("kapt")
}
android {
namespace = "com.kouros.data"
compileSdk = 36
@@ -33,13 +32,14 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
}
dependencies {
implementation(libs.android.sdk.turf)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
@@ -47,6 +47,9 @@ dependencies {
implementation(libs.koin.core)
implementation(libs.koin.android)
implementation(libs.koin.compose.viewmodel)
implementation(libs.androidx.car.app)
implementation(libs.android.sdk.turf)
// objectbox
implementation(libs.objectbox.kotlin)

View File

@@ -16,19 +16,12 @@
package com.kouros.navigation.data
import android.R
import android.location.Location
import android.location.LocationManager
import android.net.Uri
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.valhalla.Maneuvers
import com.kouros.navigation.data.valhalla.ValhallaJson
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import kotlinx.serialization.Serializable
import org.maplibre.geojson.Point
data class Category(
val id: String,
@@ -61,7 +54,16 @@ data class ContactData(
data class StepData (
var instruction: String,
var leftDistance: Double,
var leftStepDistance: Double,
var maneuverType: Int,
var icon: Int,
var arrivalTime : Long,
var leftDistance: Double
)

View File

@@ -2,7 +2,12 @@ package com.kouros.navigation.model
import android.location.Location
import android.location.LocationManager
import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Step
import com.kouros.data.R
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.ManeuverType
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData
@@ -10,6 +15,7 @@ import com.kouros.navigation.utils.location
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
import org.maplibre.turf.TurfMeasurement
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
open class RouteModel() {
@@ -44,25 +50,51 @@ open class RouteModel() {
navigating = true
}
private fun createCenterLocation(): Location {
val future = TurfMeasurement.center(FeatureCollection.fromJson(route.routeGeoJson))
val point = future.geometry() as Point
return location(point.longitude(), point.latitude())
fun stopNavigation() {
route.clear()
navigating = false
currentShapeIndex = 0
distanceToStepEnd = 0F
beginIndex = 0
endIndex = 0
}
/**
* Calculates the geographic center of the route's GeoJSON data.
*
* @return A [Location] object representing the center point.
* @throws IllegalStateException if the calculated center does not have valid Point geometry.
*/
private fun createCenterLocation(): Location {
// 1. Create a FeatureCollection from the raw GeoJSON string.
val featureCollection = FeatureCollection.fromJson(route.routeGeoJson)
// 2. Calculate the center feature of the collection.
val centerFeature = TurfMeasurement.center(featureCollection)
// 3. Safely access and cast the geometry, throwing an informative error if it fails.
val centerPoint = centerFeature.geometry() as? Point
?: throw IllegalStateException("Center of GeoJSON is not a valid Point.")
// 4. Create and return the Location object.
return location(centerPoint.longitude(), centerPoint.latitude())
}
/**
* The remaining distance to the step, rounded to the nearest 10 units.
*/
val currentDistance: Double
/** Returns the current [Step] with information such as the cue text and images. */
get() {
return ((leftStepDistance()).roundToInt().toDouble() / 10.0).roundToInt() * 10.0
// This is a more direct way to round to the nearest multiple of 10.
return (leftStepDistance() / 10.0).roundToInt() * 10.0
}
fun updateLocation(location: Location) {
var nearestDistance = 100000.0f
route.currentManeuverIndex = -1
// find maneuver
for ((i, maneuver) in route.maneuvers.withIndex()) {
for (i in route.currentManeuverIndex..<route.maneuvers.size) {
val maneuver = route.maneuvers[i]
val beginShapeIndex = maneuver.beginShapeIndex
val endShapeIndex = maneuver.endShapeIndex
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
@@ -74,30 +106,6 @@ open class RouteModel() {
}
}
fun currentStep(): StepData {
val maneuver = route.currentManeuver()
var text = ""
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
val curLocation = location(
route.pointLocations[currentShapeIndex].longitude(),
route.pointLocations[currentShapeIndex].latitude()
)
val distanceStepLeft = leftStepDistance()
when (distanceStepLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
}
}
}
return StepData(text, distanceStepLeft)
}
/** Calculates the index in a maneuver. */
private fun calculateCurrentShapeIndex(
beginShapeIndex: Int,
@@ -105,7 +113,7 @@ open class RouteModel() {
location: Location
) {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
for (i in currentShapeIndex..endShapeIndex) {
val waypoint = Location(LocationManager.GPS_PROVIDER)
waypoint.longitude = route.waypoints[i][0]
waypoint.latitude = route.waypoints[i][1]
@@ -131,6 +139,97 @@ open class RouteModel() {
}
}
fun currentStep(): StepData {
// Determine if we should display the current or the next maneuver
val distanceToNextStep = leftStepDistance()
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
val shouldAdvance =
isNearNextManeuver && route.currentManeuverIndex < (route.maneuvers.size - 1)
// Get the single, correct maneuver for this state
val relevantManeuver = if (shouldAdvance) {
route.nextManeuver() // This advances the route's state
} else {
route.currentManeuver()
}
// Safely get the street name from the maneuver
val streetName = relevantManeuver.streetNames?.firstOrNull() ?: ""
// Determine the maneuver type and corresponding icon
val maneuverType = if (hasArrived(relevantManeuver.type)) {
relevantManeuver.type
} else {
ManeuverType.None.value
}
val maneuverIconPair = maneuverIcon(maneuverType)
// Construct and return the final StepData object
return StepData(
streetName,
distanceToNextStep,
maneuverIconPair.first,
maneuverIconPair.second,
arrivalTime(),
travelLeftDistance()
)
}
fun currentStepx(): StepData {
val maneuver = route.currentManeuver()
var text = ""
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
val distanceStepLeft = leftStepDistance()
when (distanceStepLeft) {
in 0.0..Constants.NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0]
}
}
}
}
val type = if (hasArrived(maneuverType)) {
maneuver.type
} else {
ManeuverType.None.value
}
var routing: (Pair<Int, Int>) = maneuverIcon(type)
when (distanceStepLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
if (route.currentManeuverIndex < route.maneuvers.size) {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
routing = maneuverIcon(maneuverType)
}
}
}
return StepData(text, distanceStepLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance())
}
fun nextStep(): StepData {
val maneuver = route.nextManeuver()
val maneuverType = maneuver.type
val distanceLeft = leftStepDistance()
var text = ""
when (distanceLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> {
}
else -> {
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
text = maneuver.streetNames[0]
}
}
}
val routing: (Pair<Int, Int>) = maneuverIcon(maneuverType)
return StepData(text, distanceLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance())
}
private fun calculateDistance(
beginShapeIndex: Int,
endShapeIndex: Int,
@@ -138,9 +237,7 @@ open class RouteModel() {
): Float {
var nearestLocation = 100000.0f
for (i in beginShapeIndex..endShapeIndex) {
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
polylineLocation.longitude = route.waypoints[i][0]
polylineLocation.latitude = route.waypoints[i][1]
val polylineLocation = location(route.waypoints[i][0], route.waypoints[i][1])
val distance: Float = location.distanceTo(polylineLocation)
if (distance < nearestLocation) {
nearestLocation = distance
@@ -165,12 +262,21 @@ open class RouteModel() {
return timeLeft
}
fun arrivalTime(): Long {
val timeLeft = travelLeftTime()
// Calculate the time to destination from the current time.
val nowUtcMillis = System.currentTimeMillis()
val timeToDestinationMillis =
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
return nowUtcMillis + timeToDestinationMillis
}
/** Returns the current [Step] left distance in m. */
fun leftStepDistance(): Double {
val maneuver = route.currentManeuver()
var leftDistance = maneuver.length
if (endIndex > 0) {
leftDistance = distanceToStepEnd.toDouble()
leftDistance = (distanceToStepEnd / 1000).toDouble()
}
return leftDistance * 1000
}
@@ -192,6 +298,72 @@ open class RouteModel() {
return leftDistance
}
fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) {
var type = Maneuver.TYPE_DEPART
var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) {
ManeuverType.None.value -> {
type = Maneuver.TYPE_STRAIGHT
currentTurnIcon = R.drawable.ic_turn_name_change
}
ManeuverType.Destination.value,
ManeuverType.DestinationRight.value,
ManeuverType.DestinationLeft.value,
-> {
type = Maneuver.TYPE_DESTINATION
currentTurnIcon = R.drawable.ic_turn_destination
}
ManeuverType.Right.value -> {
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
currentTurnIcon = R.drawable.ic_turn_normal_right
}
ManeuverType.Left.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = R.drawable.ic_turn_normal_left
}
ManeuverType.RampRight.value -> {
type = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
currentTurnIcon = R.drawable.ic_turn_slight_right
}
ManeuverType.RampLeft.value -> {
type = Maneuver.TYPE_TURN_NORMAL_LEFT
currentTurnIcon = R.drawable.ic_turn_normal_left
}
ManeuverType.ExitRight.value -> {
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
currentTurnIcon = R.drawable.ic_turn_slight_right
}
ManeuverType.StayRight.value -> {
type = Maneuver.TYPE_KEEP_RIGHT
currentTurnIcon = R.drawable.ic_turn_name_change
}
ManeuverType.StayLeft.value -> {
type = Maneuver.TYPE_KEEP_LEFT
currentTurnIcon = R.drawable.ic_turn_name_change
}
ManeuverType.RoundaboutEnter.value -> {
type = Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
ManeuverType.RoundaboutExit.value -> {
type = Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
currentTurnIcon = R.drawable.ic_roundabout_ccw
}
}
maneuverType = type
return Pair(type, currentTurnIcon)
}
fun isNavigating(): Boolean {
return navigating
}
@@ -200,12 +372,9 @@ open class RouteModel() {
return arrived
}
fun stopNavigation() {
route.clear()
navigating = false
currentShapeIndex = 0
distanceToStepEnd = 0F
beginIndex = 0
endIndex = 0
fun hasArrived(type: Int): Boolean {
return type == ManeuverType.DestinationRight.value
|| maneuverType == ManeuverType.Destination.value
|| maneuverType == ManeuverType.DestinationLeft.value
}
}

View File

@@ -13,10 +13,17 @@ import org.maplibre.geojson.Point
import org.maplibre.turf.TurfMisc
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sin
@@ -191,4 +198,16 @@ fun location(longitude : Double, latitude: Double): Location {
location.longitude = longitude
location.latitude = latitude
return location
}
fun formatDateTime(time: Long): String {
val dateFormatter = DateTimeFormatter.ofLocalizedTime( FormatStyle.SHORT)
val dateTime = LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC)
val zdt = ZonedDateTime.of(dateTime, ZoneId.of("Europe/Berlin"))
return zdt.format(dateFormatter)
}
fun Double.round(numFractionDigits: Int): Double {
val factor = 10.0.pow(numFractionDigits.toDouble())
return (this * factor).roundToInt() / factor
}