Skip to content

Handle Map Click Events

This tutorial covers handling tap, long-press, and feature click events on your MapMetrics Android map.

Prerequisites

Basic Map Click

Respond when the user taps anywhere on the map:

kotlin
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.annotations.MarkerOptions
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 MapClickActivity : AppCompatActivity() {

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

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

        coordsText = findViewById(R.id.tvCoords)
        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"
                )
            ) {
                setupClickListeners()
            }

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

    private fun setupClickListeners() {
        // Single tap
        map.addOnMapClickListener { latLng ->
            coordsText.text = String.format(
                "Lat: %.6f, Lng: %.6f",
                latLng.latitude, latLng.longitude
            )
            true
        }

        // Long press
        map.addOnMapLongClickListener { latLng ->
            map.addMarker(
                MarkerOptions()
                    .position(latLng)
                    .title("Dropped Pin")
                    .snippet(
                        String.format(
                            "%.6f, %.6f",
                            latLng.latitude, latLng.longitude
                        )
                    )
            )
            Toast.makeText(this, "Marker added!", Toast.LENGTH_SHORT).show()
            true
        }
    }

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

Query Features at Click Point

Identify which features (layers, sources) are under the tap:

kotlin
map.addOnMapClickListener { latLng ->
    val screenPoint = map.projection.toScreenLocation(latLng)

    // Query all rendered features at the tap point
    val features = map.queryRenderedFeatures(screenPoint)

    if (features.isNotEmpty()) {
        val feature = features[0]
        val properties = feature.properties()

        Toast.makeText(
            this,
            "Feature: ${properties?.toString() ?: "no properties"}",
            Toast.LENGTH_LONG
        ).show()
    } else {
        Toast.makeText(this, "No features here", Toast.LENGTH_SHORT).show()
    }

    true
}

Query Specific Layers

Only respond to taps on specific layers:

kotlin
map.addOnMapClickListener { latLng ->
    val screenPoint = map.projection.toScreenLocation(latLng)

    // Query only the "poi-layer" and "building-layer"
    val features = map.queryRenderedFeatures(
        screenPoint,
        "poi-layer",
        "building-layer"
    )

    if (features.isNotEmpty()) {
        val name = features[0].getStringProperty("name")
        Toast.makeText(this, "Tapped: $name", Toast.LENGTH_SHORT).show()
    }

    true
}

Click Priority — Markers vs Layers

Handle markers and layer features with different priority:

kotlin
private fun setupLayeredClickHandling() {
    // Marker click takes priority
    map.setOnMarkerClickListener { marker ->
        Toast.makeText(
            this,
            "Marker: ${marker.title}",
            Toast.LENGTH_SHORT
        ).show()
        true // consume — don't pass to map click
    }

    // Map click handles everything else
    map.addOnMapClickListener { latLng ->
        val screenPoint = map.projection.toScreenLocation(latLng)

        // Check custom layers
        val poiFeatures = map.queryRenderedFeatures(screenPoint, "poi-layer")
        if (poiFeatures.isNotEmpty()) {
            handlePoiClick(poiFeatures[0])
            return@addOnMapClickListener true
        }

        // Check polygon zones
        val zoneFeatures = map.queryRenderedFeatures(screenPoint, "zone-fill")
        if (zoneFeatures.isNotEmpty()) {
            handleZoneClick(zoneFeatures[0])
            return@addOnMapClickListener true
        }

        // Nothing hit — show coordinates
        coordsText.text = String.format("%.4f, %.4f", latLng.latitude, latLng.longitude)
        true
    }
}

private fun handlePoiClick(feature: org.maplibre.geojson.Feature) {
    val name = feature.getStringProperty("name") ?: "Unknown"
    Toast.makeText(this, "POI: $name", Toast.LENGTH_SHORT).show()
}

private fun handleZoneClick(feature: org.maplibre.geojson.Feature) {
    val zoneName = feature.getStringProperty("zone_name") ?: "Unknown zone"
    Toast.makeText(this, "Zone: $zoneName", Toast.LENGTH_SHORT).show()
}

Screen Coordinate Conversion

Convert between screen pixels and map coordinates:

kotlin
// LatLng → screen pixel
val screenPoint = map.projection.toScreenLocation(LatLng(48.8566, 2.3522))
// screenPoint.x, screenPoint.y in pixels

// Screen pixel → LatLng
val latLng = map.projection.fromScreenLocation(android.graphics.PointF(500f, 500f))
// latLng.latitude, latLng.longitude

// Get visible region bounds
val visibleRegion = map.projection.visibleRegion
val bounds = visibleRegion.latLngBounds
// bounds.northEast, bounds.southWest

Available Click Listeners

ListenerTriggerReturns
addOnMapClickListenerSingle tap on mapLatLng
addOnMapLongClickListenerLong press on mapLatLng
setOnMarkerClickListenerTap on annotation markerMarker
setOnInfoWindowClickListenerTap on info windowMarker
setOnInfoWindowLongClickListenerLong press on info windowMarker
setOnInfoWindowCloseListenerInfo window closesMarker

Next Steps


Tip: Return true from click listeners to consume the event and prevent it from propagating. Return false to let it pass through to the next handler. Order matters: marker listeners fire before map click listeners.