Slowly Fly to a Location
This tutorial shows how to create a cinematic slow camera flight — useful for showcases, presentations, or atmospheric map experiences.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Cinematic Slow Flight
A long-duration animated camera move with tilt and bearing changes:
kotlin
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
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 SlowFlyActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_slow_fly)
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"
)
)
// Start from a wide aerial view
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(48.8566, 2.3522)) // Paris overview
.zoom(10.0)
.tilt(0.0)
.bearing(0.0)
.build()
// Slow fly button
findViewById<Button>(R.id.btnSlowFly).setOnClickListener {
slowFlyToEiffelTower()
}
}
}
private fun slowFlyToEiffelTower() {
val destination = CameraPosition.Builder()
.target(LatLng(48.8584, 2.2945)) // Eiffel Tower
.zoom(17.0) // Street level
.tilt(60.0) // Dramatic 3D angle
.bearing(-30.0) // Slight rotation
.build()
map.animateCamera(
CameraUpdateFactory.newCameraPosition(destination),
10000 // 10 seconds — slow, cinematic
)
}
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)
}
}Multi-Stage Cinematic Sequence
Chain multiple slow flights for a full cinematic experience:
kotlin
private data class CinematicStop(
val target: LatLng,
val zoom: Double,
val tilt: Double,
val bearing: Double,
val flightDuration: Long, // ms to fly there
val pauseDuration: Long // ms to pause before next
)
private val cinematicStops = listOf(
CinematicStop(
LatLng(48.8566, 2.3522), 11.0, 0.0, 0.0,
0, 1000 // Start: Paris overview
),
CinematicStop(
LatLng(48.8584, 2.2945), 16.0, 55.0, -20.0,
8000, 3000 // Fly to Eiffel Tower
),
CinematicStop(
LatLng(48.8606, 2.3376), 16.5, 50.0, 30.0,
6000, 3000 // Fly to Louvre
),
CinematicStop(
LatLng(48.8530, 2.3499), 17.0, 60.0, -10.0,
6000, 3000 // Fly to Notre-Dame
),
CinematicStop(
LatLng(48.8867, 2.3431), 15.5, 45.0, 0.0,
8000, 2000 // Fly to Sacré-Cœur
),
)
private fun startCinematicTour() {
playCinematicStop(0)
}
private fun playCinematicStop(index: Int) {
if (index >= cinematicStops.size) return
val stop = cinematicStops[index]
val position = CameraPosition.Builder()
.target(stop.target)
.zoom(stop.zoom)
.tilt(stop.tilt)
.bearing(stop.bearing)
.build()
if (stop.flightDuration == 0L) {
// First stop — instant position
map.moveCamera(CameraUpdateFactory.newCameraPosition(position))
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
playCinematicStop(index + 1)
}, stop.pauseDuration)
} else {
map.animateCamera(
CameraUpdateFactory.newCameraPosition(position),
stop.flightDuration.toInt(),
object : MapMetricsMap.CancelableCallback {
override fun onFinish() {
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
playCinematicStop(index + 1)
}, stop.pauseDuration)
}
override fun onCancel() {
// User interrupted — stop the tour
}
}
)
}
}Slow Fly with easeCamera
Use easeCamera for a constant-speed flight (no acceleration):
kotlin
private fun slowEaseFlight() {
map.easeCamera(
CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder()
.target(LatLng(48.8584, 2.2945))
.zoom(16.0)
.tilt(50.0)
.bearing(-15.0)
.build()
),
15000 // 15 seconds, constant speed
)
}Speed Comparison
| Duration | Feel | Use Case |
|---|---|---|
| 1-2s | Quick snap | UI navigation |
| 3-5s | Normal fly-to | Standard interaction |
| 6-10s | Slow, cinematic | Showcase, guided tour |
| 10-20s | Very slow | Presentation, ambient display |
easeCamera 10s+ | Constant speed, no acceleration | Smooth documentary feel |
Next Steps
- Fly to a Location — Standard fly-to animation
- Orbit Animation — Rotating camera
- Jump to Locations — Step through waypoints
Tip: For the most cinematic effect, combine slow flights (8-15s) with tilt changes (0° → 60°) and slight bearing shifts. The simultaneous zoom + tilt + bearing creates the "Google Earth" swooping effect.