Skip to content

Fly to a Location

This tutorial shows how to animate the camera smoothly to a destination point on your MapMetrics Android map using different animation styles.

Prerequisites

Basic Fly-To Animation

Animate the camera to a new location with a button:

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 FlyToActivity : AppCompatActivity() {

    private lateinit var mapView: MapView
    private lateinit var map: MapMetricsMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fly_to)

        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"
                )
            )

            // Set initial position
            map.cameraPosition = CameraPosition.Builder()
                .target(LatLng(51.5074, -0.1278)) // London
                .zoom(10.0)
                .build()

            // Fly to Paris button
            findViewById<Button>(R.id.btnFlyToParis).setOnClickListener {
                flyToLocation(LatLng(48.8566, 2.3522), 14.0, "Paris")
            }

            // Fly to Tokyo button
            findViewById<Button>(R.id.btnFlyToTokyo).setOnClickListener {
                flyToLocation(LatLng(35.6762, 139.6503), 12.0, "Tokyo")
            }
        }
    }

    private fun flyToLocation(target: LatLng, zoom: Double, name: String) {
        val position = CameraPosition.Builder()
            .target(target)
            .zoom(zoom)
            .tilt(45.0)
            .bearing(0.0)
            .build()

        map.animateCamera(
            CameraUpdateFactory.newCameraPosition(position),
            3000 // 3 seconds duration
        )
    }

    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)
    }
}

Three Animation Styles

Compare moveCamera, easeCamera, and animateCamera:

kotlin
val target = CameraPosition.Builder()
    .target(LatLng(48.8566, 2.3522))
    .zoom(14.0)
    .tilt(45.0)
    .build()

// 1. Instant jump — no animation
map.moveCamera(CameraUpdateFactory.newCameraPosition(target))

// 2. Ease — constant speed, smooth but linear
map.easeCamera(
    CameraUpdateFactory.newCameraPosition(target),
    2000 // duration in ms
)

// 3. Animate — accelerates and decelerates naturally
map.animateCamera(
    CameraUpdateFactory.newCameraPosition(target),
    3000 // duration in ms
)

Fly-To with Callback

Run code when the animation finishes or is cancelled:

kotlin
import org.maplibre.android.maps.MapMetricsMap.CancelableCallback

map.animateCamera(
    CameraUpdateFactory.newCameraPosition(
        CameraPosition.Builder()
            .target(LatLng(40.7128, -74.0060)) // New York
            .zoom(15.0)
            .tilt(50.0)
            .bearing(-20.0)
            .build()
    ),
    4000,
    object : CancelableCallback {
        override fun onFinish() {
            // Animation completed — add marker, load data, etc.
            map.addMarker(
                org.maplibre.android.annotations.MarkerOptions()
                    .position(LatLng(40.7128, -74.0060))
                    .title("New York City")
            )
        }

        override fun onCancel() {
            // User interrupted the animation by touching the map
        }
    }
)

Tour Through Multiple Locations

Fly through a sequence of locations automatically:

kotlin
import android.os.Handler
import android.os.Looper

private val tourLocations = listOf(
    Triple(LatLng(48.8566, 2.3522), 14.0, "Paris"),
    Triple(LatLng(41.9028, 12.4964), 13.0, "Rome"),
    Triple(LatLng(52.5200, 13.4050), 12.0, "Berlin"),
    Triple(LatLng(59.3293, 18.0686), 13.0, "Stockholm"),
    Triple(LatLng(40.4168, -3.7038), 12.0, "Madrid"),
)

private var currentIndex = 0

private fun startTour() {
    if (currentIndex >= tourLocations.size) {
        currentIndex = 0 // loop back
    }

    val (target, zoom, name) = tourLocations[currentIndex]

    val position = CameraPosition.Builder()
        .target(target)
        .zoom(zoom)
        .tilt(50.0)
        .bearing(currentIndex * 30.0) // vary the bearing
        .build()

    map.animateCamera(
        CameraUpdateFactory.newCameraPosition(position),
        3000,
        object : CancelableCallback {
            override fun onFinish() {
                currentIndex++
                // Wait 2 seconds at each stop, then fly to next
                Handler(Looper.getMainLooper()).postDelayed({
                    startTour()
                }, 2000)
            }

            override fun onCancel() {
                // User touched the map — stop the tour
            }
        }
    )
}

Animation Comparison

MethodSpeedFeelUse Case
moveCameraInstantAbrupt jumpResetting view, loading saved position
easeCameraConstantSmooth, linearShort transitions, subtle moves
animateCameraEasedNatural accelerationFly-to, showcase, tours

Next Steps


Tip: For long-distance flights (e.g., Paris → Tokyo), use animateCamera with a duration of 4-6 seconds. For short hops within the same city, 1-2 seconds with easeCamera feels more natural.