Skip to content

Commit 96f10e3

Browse files
committed
Add a ReplayRouteSession
1 parent 09b5248 commit 96f10e3

15 files changed

Lines changed: 498 additions & 127 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Added ReplayHistorySession and MapboxTripStarter.enableReplayHistory()

examples/src/main/java/com/mapbox/navigation/examples/MainActivity.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import androidx.core.app.ActivityCompat
1111
import androidx.core.content.ContextCompat
1212
import androidx.recyclerview.widget.LinearLayoutManager
1313
import com.mapbox.android.core.permissions.PermissionsListener
14+
import com.mapbox.common.LogConfiguration
15+
import com.mapbox.common.LoggingLevel
1416
import com.mapbox.navigation.examples.core.IndependentRouteGenerationActivity
1517
import com.mapbox.navigation.examples.core.MapboxBuildingHighlightActivity
1618
import com.mapbox.navigation.examples.core.MapboxCustomStyleActivity
@@ -39,6 +41,7 @@ class MainActivity : AppCompatActivity(), PermissionsListener {
3941

4042
override fun onCreate(savedInstanceState: Bundle?) {
4143
super.onCreate(savedInstanceState)
44+
LogConfiguration.setLoggingLevel(LoggingLevel.DEBUG)
4245
binding = LayoutActivityMainBinding.inflate(layoutInflater)
4346
setContentView(binding.root)
4447

examples/src/main/java/com/mapbox/navigation/examples/core/ReplayHistoryActivity.kt

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import android.content.res.Configuration
66
import android.content.res.Resources
77
import android.location.Location
88
import android.os.Bundle
9-
import android.view.View
109
import android.widget.Button
1110
import android.widget.SeekBar
1211
import androidx.appcompat.app.AppCompatActivity
@@ -23,10 +22,9 @@ import com.mapbox.maps.plugin.locationcomponent.location
2322
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
2423
import com.mapbox.navigation.base.options.NavigationOptions
2524
import com.mapbox.navigation.core.MapboxNavigation
26-
import com.mapbox.navigation.core.MapboxNavigationProvider
2725
import com.mapbox.navigation.core.directions.session.RoutesObserver
28-
import com.mapbox.navigation.core.replay.MapboxReplayer
2926
import com.mapbox.navigation.core.replay.history.ReplayEventBase
27+
import com.mapbox.navigation.core.replay.history.ReplayHistorySession
3028
import com.mapbox.navigation.core.replay.history.ReplaySetNavigationRoute
3129
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
3230
import com.mapbox.navigation.core.trip.session.LocationObserver
@@ -50,21 +48,22 @@ import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions
5048
import com.mapbox.navigation.ui.maps.route.line.model.RouteLine
5149
import com.mapbox.navigation.ui.maps.route.line.model.RouteLineColorResources
5250
import com.mapbox.navigation.ui.maps.route.line.model.RouteLineResources
51+
import com.mapbox.navigation.utils.internal.logI
5352
import kotlinx.coroutines.CoroutineScope
5453
import kotlinx.coroutines.Dispatchers
5554
import kotlinx.coroutines.Job
5655
import kotlinx.coroutines.launch
57-
import java.util.Collections
56+
import java.util.*
5857

5958
private const val DEFAULT_INITIAL_ZOOM = 15.0
6059

60+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
6161
class ReplayHistoryActivity : AppCompatActivity() {
6262

6363
private var loadNavigationJob: Job? = null
6464
private val navigationLocationProvider = NavigationLocationProvider()
6565
private lateinit var historyFileLoader: HistoryFileLoader
6666
private lateinit var mapboxNavigation: MapboxNavigation
67-
private lateinit var mapboxReplayer: MapboxReplayer
6867
private lateinit var locationComponent: LocationComponentPlugin
6968
private lateinit var navigationCamera: NavigationCamera
7069
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
@@ -103,6 +102,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
103102
40.0 * pixelDensity
104103
)
105104
}
105+
private val replayHistorySession = ReplayHistorySession()
106106

107107
private val initialCameraOptions: CameraOptions? = CameraOptions.Builder()
108108
.zoom(DEFAULT_INITIAL_ZOOM)
@@ -163,7 +163,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
163163
super.onDestroy()
164164
routeLineApi.cancel()
165165
routeLineView.cancel()
166-
mapboxReplayer.finish()
166+
replayHistorySession.onDetached(mapboxNavigation)
167167
mapboxNavigation.onDestroy()
168168
if (::locationComponent.isInitialized) {
169169
locationComponent.removeOnIndicatorPositionChangedListener(onPositionChangedListener)
@@ -209,16 +209,19 @@ class ReplayHistoryActivity : AppCompatActivity() {
209209
private val locationObserver = object : LocationObserver {
210210
override fun onNewRawLocation(rawLocation: Location) {}
211211
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
212+
logI(ReplayHistorySession.LOG_CATEGORY) {
213+
"onNewLocationMatcherResult $locationMatcherResult"
214+
}
212215
viewportDataSource.onLocationChanged(locationMatcherResult.enhancedLocation)
213216
viewportDataSource.evaluate()
214217
if (!isLocationInitialized) {
215218
isLocationInitialized = true
216-
val instantTransition = NavigationCameraTransitionOptions.Builder()
217-
.maxDuration(0)
218-
.build()
219219
navigationCamera.requestNavigationCameraToOverview(
220-
stateTransitionOptions = instantTransition,
220+
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
221+
.maxDuration(0)
222+
.build(),
221223
)
224+
navigationCamera.requestNavigationCameraToFollowing()
222225
}
223226

224227
navigationLocationProvider.changePosition(
@@ -308,21 +311,12 @@ class ReplayHistoryActivity : AppCompatActivity() {
308311
@SuppressLint("MissingPermission")
309312
private fun initNavigation() {
310313
historyFileLoader = HistoryFileLoader()
311-
mapboxNavigation = MapboxNavigationProvider.create(
314+
mapboxNavigation = MapboxNavigation(
312315
NavigationOptions.Builder(this)
313316
.accessToken(Utils.getMapboxAccessToken(this))
314317
.build()
315318
)
316-
startReplayTripSession()
317-
}
318-
319-
/**
320-
* This is showcasing a new way to replay rides at runtime.
321-
*/
322-
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
323-
private fun startReplayTripSession() {
324-
mapboxReplayer = mapboxNavigation.mapboxReplayer
325-
mapboxNavigation.startReplayTripSession()
319+
replayHistorySession.onAttached(mapboxNavigation)
326320
}
327321

328322
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -336,23 +330,19 @@ class ReplayHistoryActivity : AppCompatActivity() {
336330
@SuppressLint("MissingPermission")
337331
private fun handleHistoryFileSelected() {
338332
loadNavigationJob = CoroutineScope(Dispatchers.Main).launch {
339-
val events = historyFileLoader
340-
.loadReplayHistory(this@ReplayHistoryActivity)
341-
mapboxReplayer.clearEvents()
342-
mapboxReplayer.pushEvents(events)
343-
binding.playReplay.visibility = View.VISIBLE
344-
mapboxNavigation.resetTripSession()
345-
mapboxNavigation.setRoutes(emptyList())
333+
val historyReader = historyFileLoader.loadReplayHistory(
334+
this@ReplayHistoryActivity
335+
)
336+
replayHistorySession.setHistoryFile(historyReader.filePath)
346337
isLocationInitialized = false
347-
mapboxReplayer.playFirstLocation()
348338
}
349339
}
350340

351341
@SuppressLint("SetTextI18n")
352342
private fun updateReplayStatus(playbackEvents: List<ReplayEventBase>) {
353343
playbackEvents.lastOrNull()?.eventTimestamp?.let {
354-
val currentSecond = mapboxReplayer.eventSeconds(it).toInt()
355-
val durationSecond = mapboxReplayer.durationSeconds().toInt()
344+
val currentSecond = mapboxNavigation.mapboxReplayer.eventSeconds(it).toInt()
345+
val durationSecond = mapboxNavigation.mapboxReplayer.durationSeconds().toInt()
356346
binding.playerStatus.text = "$currentSecond:$durationSecond"
357347
}
358348
}
@@ -368,7 +358,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
368358
binding.seekBar.setOnSeekBarChangeListener(
369359
object : SeekBar.OnSeekBarChangeListener {
370360
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
371-
mapboxReplayer.playbackSpeed(progress.toDouble())
361+
mapboxNavigation.mapboxReplayer.playbackSpeed(progress.toDouble())
372362
binding.seekBarText.text = getString(
373363
R.string.replay_playback_speed_seekbar,
374364
progress
@@ -380,13 +370,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
380370
}
381371
)
382372

383-
binding.playReplay.setOnClickListener {
384-
mapboxReplayer.play()
385-
binding.playReplay.visibility = View.GONE
386-
navigationCamera.requestNavigationCameraToFollowing()
387-
}
388-
389-
mapboxReplayer.registerObserver { events ->
373+
mapboxNavigation.mapboxReplayer.registerObserver { events ->
390374
updateReplayStatus(events)
391375
events.forEach {
392376
when (it) {

examples/src/main/java/com/mapbox/navigation/examples/core/replay/HistoryFileLoader.kt

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,24 @@ import kotlinx.coroutines.Dispatchers
1010
import kotlinx.coroutines.withContext
1111

1212
class HistoryFileLoader {
13-
private val replayHistoryMapper = ReplayHistoryMapper.Builder().setRouteMapper {
14-
ReplaySetNavigationRoute.Builder(eventTimestamp = it.eventTimestamp)
15-
.route(it.navigationRoute)
16-
.build()
17-
}.build()
1813
private val historyFilesDirectory = HistoryFilesDirectory()
1914

2015
@SuppressLint("MissingPermission")
2116
suspend fun loadReplayHistory(
2217
context: Context
23-
): List<ReplayEventBase> = withContext(Dispatchers.IO) {
24-
loadSelectedHistory() ?: loadDefaultReplayHistory(context)
18+
): MapboxHistoryReader = withContext(Dispatchers.IO) {
19+
HistoryFilesActivity.selectedHistory ?: loadDefaultReplayHistory(context)
2520
}
2621

27-
private suspend fun loadSelectedHistory(): List<ReplayEventBase>? =
28-
withContext(Dispatchers.IO) {
29-
HistoryFilesActivity.selectedHistory?.asSequence()?.mapNotNull { historyEvent ->
30-
replayHistoryMapper.mapToReplayEvent(historyEvent)
31-
}?.toList()
32-
}
33-
3422
private suspend fun loadDefaultReplayHistory(
3523
context: Context
36-
): List<ReplayEventBase> = withContext(Dispatchers.IO) {
24+
): MapboxHistoryReader = withContext(Dispatchers.IO) {
3725
val fileName = "replay-history-activity.json"
3826
val inputStream = context.assets.open(fileName)
3927
val outputFile = historyFilesDirectory.outputFile(context, fileName)
4028
outputFile.outputStream().use { fileOut ->
4129
inputStream.copyTo(fileOut)
4230
}
4331
MapboxHistoryReader(outputFile.absolutePath)
44-
.asSequence()
45-
.mapNotNull { replayHistoryMapper.mapToReplayEvent(it) }
46-
.toList()
4732
}
4833
}

examples/src/main/res/layout/activity_replay_history_layout.xml

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,11 @@
3434
android:text="@string/select_history"
3535
/>
3636

37-
<androidx.appcompat.widget.AppCompatButton
38-
android:id="@+id/playReplay"
39-
android:layout_width="0dp"
40-
android:layout_height="wrap_content"
41-
app:layout_constraintEnd_toEndOf="parent"
42-
app:layout_constraintStart_toStartOf="parent"
43-
app:layout_constraintBottom_toBottomOf="parent"
44-
android:background="@color/colorPrimary"
45-
android:text="@string/play_history"
46-
android:textColor="@android:color/white"
47-
/>
48-
4937
<com.google.android.material.card.MaterialCardView
5038
android:layout_width="match_parent"
5139
android:layout_height="wrap_content"
5240
android:theme="@style/Theme.MaterialComponents.Light"
53-
app:layout_constraintBottom_toTopOf="@id/playReplay"
41+
app:layout_constraintBottom_toBottomOf="parent"
5442
app:cardElevation="3dp"
5543
app:cardUseCompatPadding="true">
5644

libnavigation-core/api/current.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ package com.mapbox.navigation.core.history {
270270
method public String getFilePath();
271271
method public boolean hasNext();
272272
method public com.mapbox.navigation.core.history.model.HistoryEvent next();
273+
method public java.util.List<com.mapbox.navigation.core.history.model.HistoryEvent> takeLocations(int count);
273274
property public final String filePath;
274275
}
275276

@@ -529,6 +530,9 @@ package com.mapbox.navigation.core.replay.history {
529530
property public final Double? time;
530531
}
531532

533+
public final class ReplayEventLocationMapperKt {
534+
}
535+
532536
public final class ReplayEventUpdateLocation implements com.mapbox.navigation.core.replay.history.ReplayEventBase {
533537
ctor public ReplayEventUpdateLocation(@com.google.gson.annotations.SerializedName("event_timestamp") double eventTimestamp, @com.google.gson.annotations.SerializedName("location") com.mapbox.navigation.core.replay.history.ReplayEventLocation location);
534538
method public double component1();
@@ -570,6 +574,36 @@ package com.mapbox.navigation.core.replay.history {
570574
method public com.mapbox.navigation.core.replay.history.ReplayHistoryMapper.Builder statusMapper(com.mapbox.navigation.core.replay.history.ReplayHistoryEventMapper<com.mapbox.navigation.core.history.model.HistoryEventGetStatus>? statusMapper);
571575
}
572576

577+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class ReplayHistorySession implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver {
578+
ctor public ReplayHistorySession();
579+
method public kotlinx.coroutines.flow.StateFlow<com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions> getOptions();
580+
method public inline <T, R> kotlinx.coroutines.flow.Flow<R> mapDistinct(kotlinx.coroutines.flow.Flow<? extends T>, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super R>,?> transform);
581+
method public void onAttached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
582+
method public void onDetached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
583+
method public void setHistoryFile(String absolutePath);
584+
method public void setOptions(com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions options);
585+
field public static final com.mapbox.navigation.core.replay.history.ReplayHistorySession.Companion Companion;
586+
field public static final String LOG_CATEGORY = "MapboxReplayHistorySession";
587+
}
588+
589+
public static final class ReplayHistorySession.Companion {
590+
}
591+
592+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class ReplayHistorySessionOptions {
593+
method public String? getFilePath();
594+
method public com.mapbox.navigation.core.replay.history.ReplayHistoryMapper getReplayHistoryMapper();
595+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder toBuilder();
596+
property public final String? filePath;
597+
property public final com.mapbox.navigation.core.replay.history.ReplayHistoryMapper replayHistoryMapper;
598+
}
599+
600+
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public static final class ReplayHistorySessionOptions.Builder {
601+
ctor public ReplayHistorySessionOptions.Builder();
602+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions build();
603+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder filePath(String? filePath);
604+
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder replayHistoryMapper(com.mapbox.navigation.core.replay.history.ReplayHistoryMapper replayHistoryMapper);
605+
}
606+
573607
public final class ReplaySetNavigationRoute implements com.mapbox.navigation.core.replay.history.ReplayEventBase {
574608
method public double getEventTimestamp();
575609
method public com.mapbox.navigation.base.route.NavigationRoute? getRoute();
@@ -1027,6 +1061,7 @@ package com.mapbox.navigation.core.trip {
10271061
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class MapboxTripStarter implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver {
10281062
method public static com.mapbox.navigation.core.trip.MapboxTripStarter create();
10291063
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableMapMatching();
1064+
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayHistory(com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions? options = null);
10301065
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayRoute(com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions? options = null);
10311066
method public static com.mapbox.navigation.core.trip.MapboxTripStarter getRegisteredInstance();
10321067
method public com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions getReplayRouteSessionOptions();

libnavigation-core/src/main/java/com/mapbox/navigation/core/history/MapboxHistoryReader.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mapbox.navigation.core.history
22

33
import com.mapbox.navigation.core.history.model.HistoryEvent
44
import com.mapbox.navigation.core.history.model.HistoryEventMapper
5+
import com.mapbox.navigation.core.history.model.HistoryEventUpdateLocation
56
import com.mapbox.navigator.HistoryReader
67

78
/**
@@ -38,6 +39,26 @@ class MapboxHistoryReader(
3839
hasNext = loadNext()
3940
}
4041

42+
/**
43+
* Loads the next [count] location events from the history file and returns all of the
44+
* [HistoryEvent] in a list. The size of the list returned will often be larger than [count]
45+
* because there are other types of events in the history file.
46+
*
47+
* @param count the maximum number of [HistoryEventUpdateLocation] to take
48+
*/
49+
fun takeLocations(count: Int): List<HistoryEvent> {
50+
val historyEvents = mutableListOf<HistoryEvent>()
51+
var locationCount = 0
52+
while (locationCount < count && hasNext) {
53+
val event = next()
54+
if (event is HistoryEventUpdateLocation) {
55+
locationCount++
56+
}
57+
historyEvents.add(event)
58+
}
59+
return historyEvents
60+
}
61+
4162
private fun loadNext(): Boolean {
4263
val historyRecord = nativeHistoryReader.next()
4364
next = if (historyRecord != null) {

libnavigation-core/src/main/java/com/mapbox/navigation/core/replay/history/ReplayHistoryMapper.kt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.mapbox.navigation.core.history.model.HistoryEventSetRoute
99
import com.mapbox.navigation.core.history.model.HistoryEventUpdateLocation
1010
import com.mapbox.navigation.core.replay.MapboxReplayer
1111
import com.mapbox.navigation.core.replay.route.ReplayRouteMapper
12-
import com.mapbox.navigation.utils.internal.logW
1312

1413
/**
1514
* Mapper that can be used with [ReplayHistoryMapper].
@@ -130,14 +129,6 @@ class ReplayHistoryMapper private constructor(
130129
* @return [ReplayHistoryMapper]
131130
*/
132131
fun build(): ReplayHistoryMapper {
133-
if (setRouteMapper === DefaultSetRouteMapper) {
134-
logW(
135-
"Mapper uses a deprecated default `setRouteMapper` based on " +
136-
"`ReplaySetRoute` type. Consider switching to a mapper based on " +
137-
"`ReplayNavigationSetRoute`.",
138-
"ReplayHistoryMapper"
139-
)
140-
}
141132
return ReplayHistoryMapper(
142133
locationMapper = locationMapper,
143134
setRouteMapper = setRouteMapper,
@@ -193,10 +184,9 @@ class ReplayHistoryMapper private constructor(
193184

194185
private val DefaultSetRouteMapper =
195186
ReplayHistoryEventMapper<HistoryEventSetRoute> {
196-
ReplaySetRoute(
197-
eventTimestamp = it.eventTimestamp,
198-
route = it.directionsRoute
199-
)
187+
ReplaySetNavigationRoute.Builder(it.eventTimestamp)
188+
.route(it.navigationRoute)
189+
.build()
200190
}
201191

202192
private val DefaultStatusMapper =

0 commit comments

Comments
 (0)