Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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.MapLibreMap
import org.maplibre.android.maps.Style

class MapClickActivity : AppCompatActivity() {

    private lateinit var mapView: MapView
    private lateinit var map: MapLibreMap
    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 { maplibreMap ->
            map = maplibreMap

            map.setStyle(
                Style.Builder().fromUri(
                    "https://gateway.mapmetrics-atlas.net/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.