From 3ca1aa657c839801efc618374e2f69894545d109 Mon Sep 17 00:00:00 2001 From: Ahmed El-Helw Date: Wed, 30 Oct 2024 04:50:41 +0400 Subject: [PATCH 1/3] Implement annotation publisher on Android This patch introduces an AnnotationPublisher to publish custom annotations for Android. Fixes #316. --- .../ferrostar/core/FerrostarCore.kt | 7 ++- .../ferrostar/core/NavigationViewModel.kt | 9 ++- .../core/annotation/AnnotationPublisher.kt | 7 +++ .../core/annotation/AnnotationWrapper.kt | 9 +++ .../annotation/DefaultAnnotationPublisher.kt | 29 +++++++++ .../annotation/NoOpAnnotationPublisher.kt | 9 +++ .../ferrostar/core/annotation/Speed.kt | 9 +++ .../annotation/SpeedSerializationAdapter.kt | 61 +++++++++++++++++++ .../ferrostar/core/annotation/SpeedUnit.kt | 13 ++++ .../ValhallaOSRMAnnotationPublisher.kt | 14 +++++ .../ValhallaOSRMExtendedAnnotation.kt | 11 ++++ 11 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationPublisher.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/AnnotationWrapper.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/DefaultAnnotationPublisher.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/NoOpAnnotationPublisher.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/Speed.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedSerializationAdapter.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/SpeedUnit.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMAnnotationPublisher.kt create mode 100644 android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt 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 53571f4b..8c80ea32 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 @@ -3,6 +3,7 @@ package com.stadiamaps.ferrostar.core import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.adapter +import com.stadiamaps.ferrostar.core.annotation.valhalla.valhallaExtendedOSRMAnnotationPublisher import com.stadiamaps.ferrostar.core.service.ForegroundServiceManager import java.net.URL import java.time.Instant @@ -248,7 +249,11 @@ class FerrostarCore( locationProvider.addListener(this, _executor) - return DefaultNavigationViewModel(this, spokenInstructionObserver, locationProvider) + return DefaultNavigationViewModel( + this, + spokenInstructionObserver, + locationProvider, + valhallaExtendedOSRMAnnotationPublisher()) } /** 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 9ffa43b4..779d43b1 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 @@ -90,14 +92,17 @@ interface NavigationViewModel { class DefaultNavigationViewModel( private val ferrostarCore: FerrostarCore, private val spokenInstructionObserver: SpokenInstructionObserver? = null, - private val locationProvider: LocationProvider + private val locationProvider: LocationProvider, + private val annotationPublisher: AnnotationPublisher<*> = NoOpAnnotationPublisher() ) : ViewModel(), NavigationViewModel { private var userLocation: UserLocation? = locationProvider.lastLocation override val uiState = ferrostarCore.state - .map { coreState -> + .map { annotationPublisher.map(it) } + .map { stateWrapper -> + val coreState = stateWrapper.state val location = locationProvider.lastLocation 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..57ac958b --- /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..a7f5b041 --- /dev/null +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt @@ -0,0 +1,11 @@ +package com.stadiamaps.ferrostar.core.annotation.valhalla + +import com.squareup.moshi.Json +import com.stadiamaps.ferrostar.core.annotation.Speed + +data class ValhallaOSRMExtendedAnnotation( + @Json(name = "maxspeed") val speedLimit: Speed?, + val speed: Double?, + val distance: Double?, + val duration: Double? +) From cf1419677ffc23154baddd7742978a95b50a3ba9 Mon Sep 17 00:00:00 2001 From: Ahmed El-Helw Date: Tue, 12 Nov 2024 16:16:16 +0400 Subject: [PATCH 2/3] Apply code review suggestions --- .../ferrostar/core/annotation/NoOpAnnotationPublisher.kt | 4 ++-- .../annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) 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 index 57ac958b..2f7c388f 100644 --- 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 @@ -2,8 +2,8 @@ package com.stadiamaps.ferrostar.core.annotation import com.stadiamaps.ferrostar.core.NavigationState -class NoOpAnnotationPublisher : AnnotationPublisher { - override fun map(state: NavigationState): AnnotationWrapper { +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/valhalla/ValhallaOSRMExtendedAnnotation.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt index a7f5b041..24649cc7 100644 --- 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 @@ -4,8 +4,12 @@ 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? ) From 4c1d3e2d5ef3e22c4a20c73420f923aef917725e Mon Sep 17 00:00:00 2001 From: Ian Wagner Date: Tue, 12 Nov 2024 23:00:50 +0900 Subject: [PATCH 3/3] Convert comments to KDoc comments --- .../annotation/valhalla/ValhallaOSRMExtendedAnnotation.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 24649cc7..1608c440 100644 --- 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 @@ -4,12 +4,12 @@ import com.squareup.moshi.Json import com.stadiamaps.ferrostar.core.annotation.Speed data class ValhallaOSRMExtendedAnnotation( - // The speed limit of the segment. + /** The speed limit of the segment. */ @Json(name = "maxspeed") val speedLimit: Speed?, - // The estimated speed of travel for the segment, in meters per second. + /** The estimated speed of travel for the segment, in meters per second. */ val speed: Double?, - // The distance in meters of the segment. + /** The distance in meters of the segment. */ val distance: Double?, - // The estimated time to traverse the segment, in seconds. + /** The estimated time to traverse the segment, in seconds. */ val duration: Double? )