Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/unreleased/features/6855.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Introduced `ReplayHistorySession` and `ReplayHistorySessionOptions` to simplify the implementation for replaying history files. History can also be enabled with `MapboxTripStarter.enableReplayHistory()`. This can replay large history files in a memory efficient way.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.mapbox.android.core.permissions.PermissionsListener
import com.mapbox.common.LogConfiguration
import com.mapbox.common.LoggingLevel
import com.mapbox.navigation.examples.core.IndependentRouteGenerationActivity
import com.mapbox.navigation.examples.core.MapboxBuildingHighlightActivity
import com.mapbox.navigation.examples.core.MapboxCustomStyleActivity
Expand Down Expand Up @@ -39,6 +41,7 @@ class MainActivity : AppCompatActivity(), PermissionsListener {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LogConfiguration.setLoggingLevel(LoggingLevel.DEBUG)
binding = LayoutActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import android.content.res.Configuration
import android.content.res.Resources
import android.location.Location
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.extension.observable.eventdata.MapLoadingErrorEventData
Expand All @@ -23,11 +23,8 @@ import com.mapbox.maps.plugin.locationcomponent.location
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.MapboxNavigationProvider
import com.mapbox.navigation.core.directions.session.RoutesObserver
import com.mapbox.navigation.core.replay.MapboxReplayer
import com.mapbox.navigation.core.replay.history.ReplayEventBase
import com.mapbox.navigation.core.replay.history.ReplaySetNavigationRoute
import com.mapbox.navigation.core.replay.history.ReplayHistorySession
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
import com.mapbox.navigation.core.trip.session.LocationObserver
import com.mapbox.navigation.core.trip.session.RouteProgressObserver
Expand All @@ -50,21 +47,17 @@ import com.mapbox.navigation.ui.maps.route.line.model.MapboxRouteLineOptions
import com.mapbox.navigation.ui.maps.route.line.model.RouteLine
import com.mapbox.navigation.ui.maps.route.line.model.RouteLineColorResources
import com.mapbox.navigation.ui.maps.route.line.model.RouteLineResources
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import com.mapbox.navigation.utils.internal.logI
import kotlinx.coroutines.launch
import java.util.Collections

private const val DEFAULT_INITIAL_ZOOM = 15.0

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class ReplayHistoryActivity : AppCompatActivity() {

private var loadNavigationJob: Job? = null
private val navigationLocationProvider = NavigationLocationProvider()
private lateinit var historyFileLoader: HistoryFileLoader
private lateinit var mapboxNavigation: MapboxNavigation
private lateinit var mapboxReplayer: MapboxReplayer
private lateinit var locationComponent: LocationComponentPlugin
private lateinit var navigationCamera: NavigationCamera
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
Expand Down Expand Up @@ -103,6 +96,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
40.0 * pixelDensity
)
}
private val replayHistorySession = ReplayHistorySession()

private val initialCameraOptions: CameraOptions? = CameraOptions.Builder()
.zoom(DEFAULT_INITIAL_ZOOM)
Expand Down Expand Up @@ -163,7 +157,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
super.onDestroy()
routeLineApi.cancel()
routeLineView.cancel()
mapboxReplayer.finish()
replayHistorySession.onDetached(mapboxNavigation)
mapboxNavigation.onDestroy()
if (::locationComponent.isInitialized) {
locationComponent.removeOnIndicatorPositionChangedListener(onPositionChangedListener)
Expand Down Expand Up @@ -212,15 +206,16 @@ class ReplayHistoryActivity : AppCompatActivity() {
viewportDataSource.onLocationChanged(locationMatcherResult.enhancedLocation)
viewportDataSource.evaluate()
if (!isLocationInitialized) {
logI("ReplayHistoryActivity") {
"onNewLocationMatcherResult initialize location"
}
isLocationInitialized = true
val instantTransition = NavigationCameraTransitionOptions.Builder()
.maxDuration(0)
.build()
navigationCamera.requestNavigationCameraToOverview(
stateTransitionOptions = instantTransition,
navigationCamera.requestNavigationCameraToFollowing(
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
.maxDuration(0)
.build(),
)
}

navigationLocationProvider.changePosition(
locationMatcherResult.enhancedLocation,
locationMatcherResult.keyPoints,
Expand Down Expand Up @@ -308,21 +303,12 @@ class ReplayHistoryActivity : AppCompatActivity() {
@SuppressLint("MissingPermission")
private fun initNavigation() {
historyFileLoader = HistoryFileLoader()
mapboxNavigation = MapboxNavigationProvider.create(
mapboxNavigation = MapboxNavigation(
NavigationOptions.Builder(this)
.accessToken(Utils.getMapboxAccessToken(this))
.build()
)
startReplayTripSession()
}

/**
* This is showcasing a new way to replay rides at runtime.
*/
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
private fun startReplayTripSession() {
mapboxReplayer = mapboxNavigation.mapboxReplayer
mapboxNavigation.startReplayTripSession()
replayHistorySession.onAttached(mapboxNavigation)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Expand All @@ -335,25 +321,10 @@ class ReplayHistoryActivity : AppCompatActivity() {

@SuppressLint("MissingPermission")
private fun handleHistoryFileSelected() {
loadNavigationJob = CoroutineScope(Dispatchers.Main).launch {
val events = historyFileLoader
.loadReplayHistory(this@ReplayHistoryActivity)
mapboxReplayer.clearEvents()
mapboxReplayer.pushEvents(events)
binding.playReplay.visibility = View.VISIBLE
mapboxNavigation.resetTripSession()
mapboxNavigation.setRoutes(emptyList())
lifecycleScope.launch {
val historyReader = historyFileLoader.loadReplayHistory(this@ReplayHistoryActivity)
replayHistorySession.setHistoryFile(historyReader.filePath)
isLocationInitialized = false
mapboxReplayer.playFirstLocation()
}
}

@SuppressLint("SetTextI18n")
private fun updateReplayStatus(playbackEvents: List<ReplayEventBase>) {
playbackEvents.lastOrNull()?.eventTimestamp?.let {
val currentSecond = mapboxReplayer.eventSeconds(it).toInt()
val durationSecond = mapboxReplayer.durationSeconds().toInt()
binding.playerStatus.text = "$currentSecond:$durationSecond"
}
}

Expand All @@ -368,7 +339,7 @@ class ReplayHistoryActivity : AppCompatActivity() {
binding.seekBar.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
mapboxReplayer.playbackSpeed(progress.toDouble())
mapboxNavigation.mapboxReplayer.playbackSpeed(progress.toDouble())
binding.seekBarText.text = getString(
R.string.replay_playback_speed_seekbar,
progress
Expand All @@ -379,26 +350,5 @@ class ReplayHistoryActivity : AppCompatActivity() {
override fun onStopTrackingTouch(seekBar: SeekBar) {}
}
)

binding.playReplay.setOnClickListener {
mapboxReplayer.play()
binding.playReplay.visibility = View.GONE
navigationCamera.requestNavigationCameraToFollowing()
}

mapboxReplayer.registerObserver { events ->
updateReplayStatus(events)
events.forEach {
when (it) {
is ReplaySetNavigationRoute -> setRoute(it)
}
}
}
}

private fun setRoute(replaySetRoute: ReplaySetNavigationRoute) {
replaySetRoute.route?.let { directionRoute ->
mapboxNavigation.setNavigationRoutes(Collections.singletonList(directionRoute))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,28 @@ package com.mapbox.navigation.examples.core.replay
import android.annotation.SuppressLint
import android.content.Context
import com.mapbox.navigation.core.history.MapboxHistoryReader
import com.mapbox.navigation.core.replay.history.ReplayEventBase
import com.mapbox.navigation.core.replay.history.ReplayHistoryMapper
import com.mapbox.navigation.core.replay.history.ReplaySetNavigationRoute
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class HistoryFileLoader {
private val replayHistoryMapper = ReplayHistoryMapper.Builder().setRouteMapper {
ReplaySetNavigationRoute.Builder(eventTimestamp = it.eventTimestamp)
.route(it.navigationRoute)
.build()
}.build()
private val historyFilesDirectory = HistoryFilesDirectory()

@SuppressLint("MissingPermission")
suspend fun loadReplayHistory(
context: Context
): List<ReplayEventBase> = withContext(Dispatchers.IO) {
loadSelectedHistory() ?: loadDefaultReplayHistory(context)
): MapboxHistoryReader = withContext(Dispatchers.IO) {
HistoryFilesActivity.selectedHistory ?: loadDefaultReplayHistory(context)
}

private suspend fun loadSelectedHistory(): List<ReplayEventBase>? =
withContext(Dispatchers.IO) {
HistoryFilesActivity.selectedHistory?.asSequence()?.mapNotNull { historyEvent ->
replayHistoryMapper.mapToReplayEvent(historyEvent)
}?.toList()
}

private suspend fun loadDefaultReplayHistory(
context: Context
): List<ReplayEventBase> = withContext(Dispatchers.IO) {
): MapboxHistoryReader = withContext(Dispatchers.IO) {
val fileName = "replay-history-activity.json"
val inputStream = context.assets.open(fileName)
val outputFile = historyFilesDirectory.outputFile(context, fileName)
outputFile.outputStream().use { fileOut ->
inputStream.copyTo(fileOut)
}
MapboxHistoryReader(outputFile.absolutePath)
.asSequence()
.mapNotNull { replayHistoryMapper.mapToReplayEvent(it) }
.toList()
}
}
23 changes: 2 additions & 21 deletions examples/src/main/res/layout/activity_replay_history_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,11 @@
android:text="@string/select_history"
/>

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/playReplay"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:background="@color/colorPrimary"
android:text="@string/play_history"
android:textColor="@android:color/white"
/>

<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.MaterialComponents.Light"
app:layout_constraintBottom_toTopOf="@id/playReplay"
app:layout_constraintBottom_toBottomOf="parent"
app:cardElevation="3dp"
app:cardUseCompatPadding="true">

Expand All @@ -71,17 +59,10 @@
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="6dp"
android:paddingBottom="30dp"
android:paddingTop="6dp"
/>

<TextView
android:id="@+id/playerStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="6dp"
android:text="Paused"/>

</LinearLayout>

</com.google.android.material.card.MaterialCardView>
Expand Down
32 changes: 32 additions & 0 deletions libnavigation-core/api/current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ package com.mapbox.navigation.core.replay.history {
property public final Double? time;
}

public final class ReplayEventLocationMapperKt {
}

public final class ReplayEventUpdateLocation implements com.mapbox.navigation.core.replay.history.ReplayEventBase {
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);
method public double component1();
Expand Down Expand Up @@ -570,6 +573,33 @@ package com.mapbox.navigation.core.replay.history {
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);
}

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class ReplayHistorySession implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver {
ctor public ReplayHistorySession();
method public kotlinx.coroutines.flow.StateFlow<com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions> getOptions();
method public void onAttached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
method public void onDetached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
method public void setHistoryFile(String absolutePath);
method public void setOptions(com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions options);
}

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class ReplayHistorySessionOptions {
method public boolean getEnableSetRoute();
method public String? getFilePath();
method public com.mapbox.navigation.core.replay.history.ReplayHistoryMapper getReplayHistoryMapper();
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder toBuilder();
property public final boolean enableSetRoute;
property public final String? filePath;
property public final com.mapbox.navigation.core.replay.history.ReplayHistoryMapper replayHistoryMapper;
}

@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public static final class ReplayHistorySessionOptions.Builder {
ctor public ReplayHistorySessionOptions.Builder();
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions build();
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder enableSetRoute(boolean enableSetRoute);
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder filePath(String? filePath);
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions.Builder replayHistoryMapper(com.mapbox.navigation.core.replay.history.ReplayHistoryMapper replayHistoryMapper);
}

public final class ReplaySetNavigationRoute implements com.mapbox.navigation.core.replay.history.ReplayEventBase {
method public double getEventTimestamp();
method public com.mapbox.navigation.base.route.NavigationRoute? getRoute();
Expand Down Expand Up @@ -1027,8 +1057,10 @@ package com.mapbox.navigation.core.trip {
@com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI public final class MapboxTripStarter implements com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver {
method public static com.mapbox.navigation.core.trip.MapboxTripStarter create();
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableMapMatching();
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayHistory(com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions? options = null);
method public com.mapbox.navigation.core.trip.MapboxTripStarter enableReplayRoute(com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions? options = null);
method public static com.mapbox.navigation.core.trip.MapboxTripStarter getRegisteredInstance();
method public com.mapbox.navigation.core.replay.history.ReplayHistorySessionOptions getReplayHistorySessionOptions();
method public com.mapbox.navigation.core.replay.route.ReplayRouteSessionOptions getReplayRouteSessionOptions();
method public void onAttached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
method public void onDetached(com.mapbox.navigation.core.MapboxNavigation mapboxNavigation);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mapbox.navigation.core.history

import androidx.annotation.VisibleForTesting

@VisibleForTesting
internal object MapboxHistoryReaderProvider {
fun create(filePath: String) = MapboxHistoryReader(filePath)
}
Loading