Refactoring
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user