Skip to content

Add Navigation Controls

This tutorial shows how to add zoom buttons, compass, and location buttons as on-screen controls for your MapMetrics Android map.

Prerequisites

Built-in Compass Control

The compass appears automatically when the map is rotated and resets bearing to north on tap:

kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.camera.CameraPosition
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 NavigationControlsActivity : AppCompatActivity() {

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

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

        mapView = findViewById(R.id.mapView)
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync { mapMetricsMap ->
            map = mapMetricsMap

            val ui = map.uiSettings

            // Compass — shows when map is rotated, taps resets to north
            ui.isCompassEnabled = true
            ui.setCompassFadeFacingNorth(true) // hide when facing north

            map.setStyle(
                Style.Builder().fromUri(
                    "https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY"
                )
            )

            map.cameraPosition = CameraPosition.Builder()
                .target(LatLng(48.8566, 2.3522))
                .zoom(12.0)
                .build()
        }
    }

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

Custom Zoom Buttons

Add plus/minus zoom buttons as a floating overlay:

kotlin
import android.widget.ImageButton
import org.maplibre.android.camera.CameraUpdateFactory

private fun setupZoomControls() {
    findViewById<ImageButton>(R.id.btnZoomIn).setOnClickListener {
        map.animateCamera(CameraUpdateFactory.zoomIn(), 300)
    }

    findViewById<ImageButton>(R.id.btnZoomOut).setOnClickListener {
        map.animateCamera(CameraUpdateFactory.zoomOut(), 300)
    }
}

Layout overlay for zoom buttons:

xml
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="end|center_vertical"
    android:layout_marginEnd="16dp"
    android:orientation="vertical">

    <ImageButton
        android:id="@+id/btnZoomIn"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:src="@drawable/ic_add"
        android:background="@drawable/btn_round_bg"
        android:contentDescription="Zoom in" />

    <ImageButton
        android:id="@+id/btnZoomOut"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_marginTop="8dp"
        android:src="@drawable/ic_remove"
        android:background="@drawable/btn_round_bg"
        android:contentDescription="Zoom out" />

</LinearLayout>

My Location Button

Add a button to fly to the user's GPS location:

kotlin
import android.Manifest
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import org.maplibre.android.location.LocationComponentActivationOptions
import org.maplibre.android.location.modes.CameraMode

private fun setupLocationButton(style: Style) {
    findViewById<ImageButton>(R.id.btnMyLocation).setOnClickListener {
        if (ActivityCompat.checkSelfPermission(
                this, Manifest.permission.ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            enableLocationAndFly(style)
        } else {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                LOCATION_REQUEST_CODE
            )
        }
    }
}

private fun enableLocationAndFly(style: Style) {
    val locationComponent = map.locationComponent

    locationComponent.activateLocationComponent(
        LocationComponentActivationOptions.builder(this, style)
            .useDefaultLocationEngine(true)
            .build()
    )
    locationComponent.isLocationComponentEnabled = true
    locationComponent.cameraMode = CameraMode.TRACKING

    // Fly to current location
    locationComponent.lastKnownLocation?.let { location ->
        map.animateCamera(
            CameraUpdateFactory.newLatLngZoom(
                LatLng(location.latitude, location.longitude),
                15.0
            ),
            1500
        )
    }
}

companion object {
    private const val LOCATION_REQUEST_CODE = 1001
}

Reset North Button

Reset bearing and tilt to default:

kotlin
private fun setupResetNorthButton() {
    findViewById<ImageButton>(R.id.btnNorth).setOnClickListener {
        map.animateCamera(
            CameraUpdateFactory.newCameraPosition(
                CameraPosition.Builder(map.cameraPosition)
                    .bearing(0.0)
                    .tilt(0.0)
                    .build()
            ),
            500
        )
    }
}

Complete Control Panel

Combine all controls in one overlay:

xml
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="end|center_vertical"
    android:layout_marginEnd="12dp"
    android:orientation="vertical"
    android:background="@drawable/controls_bg"
    android:padding="4dp">

    <ImageButton
        android:id="@+id/btnZoomIn"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/ic_add"
        android:background="?selectableItemBackground"
        android:contentDescription="Zoom in" />

    <ImageButton
        android:id="@+id/btnZoomOut"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/ic_remove"
        android:background="?selectableItemBackground"
        android:contentDescription="Zoom out" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#DDDDDD"
        android:layout_marginVertical="4dp" />

    <ImageButton
        android:id="@+id/btnNorth"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/ic_compass"
        android:background="?selectableItemBackground"
        android:contentDescription="Reset north" />

    <ImageButton
        android:id="@+id/btnMyLocation"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/ic_my_location"
        android:background="?selectableItemBackground"
        android:contentDescription="My location" />

</LinearLayout>

Compass Settings

SettingXML AttributeDescription
isCompassEnabledmaplibre_uiCompassShow/hide compass
setCompassFadeFacingNorthmaplibre_uiCompassFadeFacingNorthAuto-hide when north up
compassGravitymaplibre_uiCompassGravityScreen position

Next Steps


Tip: On mobile, keep navigation controls small (40-48dp) and place them on the right edge so they don't overlap the map's built-in compass (top-left by default) or attribution (bottom-left).