Jump Through a Series of Locations
This tutorial shows how to navigate through a list of locations one by one — useful for guided tours, location lists, or step-by-step waypoints.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Step Through Locations
Navigate forward and backward through a list:
kotlin
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.annotations.MarkerOptions
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
class JumpLocationsActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
private lateinit var locationLabel: TextView
data class Location(
val position: LatLng,
val name: String,
val description: String,
val zoom: Double
)
private val locations = listOf(
Location(LatLng(48.8584, 2.2945), "Eiffel Tower", "324m iron lattice tower", 16.0),
Location(LatLng(48.8606, 2.3376), "Louvre Museum", "World's largest art museum", 16.0),
Location(LatLng(48.8530, 2.3499), "Notre-Dame", "Medieval Catholic cathedral", 17.0),
Location(LatLng(48.8738, 2.2950), "Arc de Triomphe", "Triumphal arch monument", 16.0),
Location(LatLng(48.8867, 2.3431), "Sacré-Cœur", "White-domed basilica", 16.0),
Location(LatLng(48.8462, 2.3464), "Luxembourg Gardens", "Historic public garden", 15.0),
)
private var currentIndex = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_jump_locations)
locationLabel = findViewById(R.id.tvLocation)
mapView = findViewById(R.id.mapView)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync { mapMetricsMap ->
map = mapMetricsMap
map.setStyle(
Style.Builder().fromUri(
"https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY"
)
) { style ->
// Add markers for all locations
for (loc in locations) {
map.addMarker(
MarkerOptions()
.position(loc.position)
.title(loc.name)
.snippet(loc.description)
)
}
// Go to first location
jumpToLocation(0)
}
// Navigation buttons
findViewById<Button>(R.id.btnPrev).setOnClickListener {
if (currentIndex > 0) jumpToLocation(currentIndex - 1)
}
findViewById<Button>(R.id.btnNext).setOnClickListener {
if (currentIndex < locations.size - 1) jumpToLocation(currentIndex + 1)
}
}
}
private fun jumpToLocation(index: Int) {
currentIndex = index
val loc = locations[index]
// Update label
locationLabel.text = "${index + 1}/${locations.size} — ${loc.name}"
// Animate camera
map.animateCamera(
CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder()
.target(loc.position)
.zoom(loc.zoom)
.tilt(45.0)
.bearing(0.0)
.build()
),
1500
)
// Update button states
findViewById<Button>(R.id.btnPrev).isEnabled = index > 0
findViewById<Button>(R.id.btnNext).isEnabled = index < locations.size - 1
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { super.onPause(); mapView.onPause() }
override fun onStop() { super.onStop(); mapView.onStop() }
override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}Layout (res/layout/activity_jump_locations.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.maplibre.android.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#DD000000"
android:padding="16dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btnPrev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="← Previous" />
<TextView
android:id="@+id/tvLocation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="16sp" />
<Button
android:id="@+id/btnNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next →" />
</LinearLayout>
</FrameLayout>Auto-Play Tour
Automatically cycle through locations:
kotlin
import android.os.Handler
import android.os.Looper
private val autoPlayHandler = Handler(Looper.getMainLooper())
private var isAutoPlaying = false
private fun startAutoPlay() {
isAutoPlaying = true
val autoPlayRunnable = object : Runnable {
override fun run() {
if (!isAutoPlaying) return
val nextIndex = (currentIndex + 1) % locations.size
jumpToLocation(nextIndex)
autoPlayHandler.postDelayed(this, 4000) // 4s per stop
}
}
autoPlayHandler.postDelayed(autoPlayRunnable, 4000)
}
private fun stopAutoPlay() {
isAutoPlaying = false
autoPlayHandler.removeCallbacksAndMessages(null)
}Next Steps
- Fly to a Location — Single fly-to animation
- Orbit Animation — Rotating camera
- Multiple Markers — Adding many markers
Tip: Vary the zoom level per location for a more engaging tour — zoom in close (17-18) for small landmarks and zoom out (13-14) for districts or parks.