diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt index 59042320..cb7a0713 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/FerrostarCore.kt @@ -220,8 +220,6 @@ class FerrostarCore( * @param route the route to navigate. * @param config change the configuration in the core before staring navigation. This was * originally provided on init, but you can set a new value for future sessions. - * @return a view model tied to the navigation session. This can be ignored if you're injecting - * the [NavigationViewModel]/[DefaultNavigationViewModel]. * @throws UserLocationUnknown if the location provider has no last known location. */ @Throws(UserLocationUnknown::class) diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt index a5512d5d..7dbd9121 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt @@ -3,6 +3,8 @@ package com.stadiamaps.ferrostar.core import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.stadiamaps.ferrostar.core.annotation.AnnotationPublisher +import com.stadiamaps.ferrostar.core.annotation.NoOpAnnotationPublisher import com.stadiamaps.ferrostar.core.extensions.currentRoadName import com.stadiamaps.ferrostar.core.extensions.deviation import com.stadiamaps.ferrostar.core.extensions.progress @@ -104,6 +106,7 @@ interface NavigationViewModel { */ class DefaultNavigationViewModel( private val ferrostarCore: FerrostarCore, + private val annotationPublisher: AnnotationPublisher<*> = NoOpAnnotationPublisher() ) : ViewModel(), NavigationViewModel { private val muteState: StateFlow = @@ -111,7 +114,9 @@ class DefaultNavigationViewModel( override val uiState = combine(ferrostarCore.state, muteState) { a, b -> a to b } - .map { (coreState, muteState) -> + .map { (coreState, muteState) -> annotationPublisher.map(coreState) to muteState } + .map { (stateWrapper, muteState) -> + val coreState = stateWrapper.state val location = ferrostarCore.locationProvider.lastLocation val userLocation = when (coreState.tripState) { diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationPublisher.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationPublisher.kt new file mode 100644 index 00000000..110f7c5d --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationPublisher.kt @@ -0,0 +1,7 @@ +package com.stadiamaps.ferrostar.core.annotation + +import com.stadiamaps.ferrostar.core.NavigationState + +interface AnnotationPublisher { + fun map(state: NavigationState): AnnotationWrapper +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationWrapper.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationWrapper.kt new file mode 100644 index 00000000..4170c900 --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationWrapper.kt @@ -0,0 +1,9 @@ +package com.stadiamaps.ferrostar.core.annotation + +import com.stadiamaps.ferrostar.core.NavigationState + +data class AnnotationWrapper( + val annotation: T? = null, + val speed: Speed? = null, + val state: NavigationState +) diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/DefaultAnnotationPublisher.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/DefaultAnnotationPublisher.kt new file mode 100644 index 00000000..7acef01c --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/DefaultAnnotationPublisher.kt @@ -0,0 +1,29 @@ +package com.stadiamaps.ferrostar.core.annotation + +import com.squareup.moshi.JsonAdapter +import com.stadiamaps.ferrostar.core.NavigationState +import uniffi.ferrostar.TripState + +class DefaultAnnotationPublisher( + private val adapter: JsonAdapter, + private val speedLimitMapper: (T?) -> Speed?, +) : AnnotationPublisher { + + override fun map(state: NavigationState): AnnotationWrapper { + val annotations = decodeAnnotations(state) + return AnnotationWrapper(annotations, speedLimitMapper(annotations), state) + } + + private fun decodeAnnotations(state: NavigationState): T? { + return if (state.tripState is TripState.Navigating) { + val json = state.tripState.annotationJson + if (json != null) { + adapter.fromJson(json) + } else { + null + } + } else { + null + } + } +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/NoOpAnnotationPublisher.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/NoOpAnnotationPublisher.kt new file mode 100644 index 00000000..2f7c388f --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/NoOpAnnotationPublisher.kt @@ -0,0 +1,9 @@ +package com.stadiamaps.ferrostar.core.annotation + +import com.stadiamaps.ferrostar.core.NavigationState + +class NoOpAnnotationPublisher : AnnotationPublisher { + override fun map(state: NavigationState): AnnotationWrapper { + return AnnotationWrapper(state = state) + } +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/Speed.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/Speed.kt new file mode 100644 index 00000000..ec87f28b --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/Speed.kt @@ -0,0 +1,9 @@ +package com.stadiamaps.ferrostar.core.annotation + +sealed class Speed { + data object NoLimit : Speed() + + data object Unknown : Speed() + + data class Value(val value: Double, val unit: SpeedUnit) : Speed() +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedSerializationAdapter.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedSerializationAdapter.kt new file mode 100644 index 00000000..a7bcf136 --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedSerializationAdapter.kt @@ -0,0 +1,61 @@ +package com.stadiamaps.ferrostar.core.annotation + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson + +class SpeedSerializationAdapter : JsonAdapter() { + + @ToJson + override fun toJson(writer: JsonWriter, speed: Speed?) { + if (speed == null) { + writer.nullValue() + } else { + writer.beginObject() + when (speed) { + is Speed.NoLimit -> writer.name("none").value(true) + is Speed.Unknown -> writer.name("unknown").value(true) + is Speed.Value -> + writer.name("value").value(speed.value).name("unit").value(speed.unit.text) + } + writer.endObject() + } + } + + @FromJson + override fun fromJson(reader: JsonReader): Speed { + reader.beginObject() + var unknown: Boolean? = null + var none: Boolean? = null + var value: Double? = null + var unit: String? = null + + while (reader.hasNext()) { + when (reader.selectName(JsonReader.Options.of("none", "unknown", "value", "unit"))) { + 0 -> none = reader.nextBoolean() + 1 -> unknown = reader.nextBoolean() + 2 -> value = reader.nextDouble() + 3 -> unit = reader.nextString() + else -> reader.skipName() + } + } + reader.endObject() + + return if (none == true) { + Speed.NoLimit + } else if (unknown == true) { + Speed.Unknown + } else if (value != null && unit != null) { + val speed = SpeedUnit.fromString(unit) + if (speed != null) { + Speed.Value(value, speed) + } else { + throw IllegalArgumentException("Invalid speed unit: $unit") + } + } else { + throw IllegalArgumentException("Invalid max speed") + } + } +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedUnit.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedUnit.kt new file mode 100644 index 00000000..ba95b233 --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedUnit.kt @@ -0,0 +1,13 @@ +package com.stadiamaps.ferrostar.core.annotation + +enum class SpeedUnit(val text: String) { + KILOMETERS_PER_HOUR("km/h"), + MILES_PER_HOUR("mph"), + KNOTS("knots"); + + companion object { + fun fromString(text: String): SpeedUnit? { + return SpeedUnit.entries.firstOrNull { it.text == text } + } + } +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMAnnotationPublisher.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMAnnotationPublisher.kt new file mode 100644 index 00000000..8e25f8ee --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMAnnotationPublisher.kt @@ -0,0 +1,14 @@ +package com.stadiamaps.ferrostar.core.annotation.valhalla + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.stadiamaps.ferrostar.core.annotation.AnnotationPublisher +import com.stadiamaps.ferrostar.core.annotation.DefaultAnnotationPublisher +import com.stadiamaps.ferrostar.core.annotation.SpeedSerializationAdapter + +fun valhallaExtendedOSRMAnnotationPublisher(): AnnotationPublisher { + val moshi = + Moshi.Builder().add(SpeedSerializationAdapter()).add(KotlinJsonAdapterFactory()).build() + val adapter = moshi.adapter(ValhallaOSRMExtendedAnnotation::class.java) + return DefaultAnnotationPublisher(adapter) { it?.speedLimit } +} diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt new file mode 100644 index 00000000..1608c440 --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt @@ -0,0 +1,15 @@ +package com.stadiamaps.ferrostar.core.annotation.valhalla + +import com.squareup.moshi.Json +import com.stadiamaps.ferrostar.core.annotation.Speed + +data class ValhallaOSRMExtendedAnnotation( + /** The speed limit of the segment. */ + @Json(name = "maxspeed") val speedLimit: Speed?, + /** The estimated speed of travel for the segment, in meters per second. */ + val speed: Double?, + /** The distance in meters of the segment. */ + val distance: Double?, + /** The estimated time to traverse the segment, in seconds. */ + val duration: Double? +)