Handle Map Click Events
This tutorial covers handling tap, long-press, and feature click events on your MapMetrics Android map.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
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.southWestAvailable Click Listeners
| Listener | Trigger | Returns |
|---|---|---|
addOnMapClickListener | Single tap on map | LatLng |
addOnMapLongClickListener | Long press on map | LatLng |
setOnMarkerClickListener | Tap on annotation marker | Marker |
setOnInfoWindowClickListener | Tap on info window | Marker |
setOnInfoWindowLongClickListener | Long press on info window | Marker |
setOnInfoWindowCloseListener | Info window closes | Marker |
Next Steps
- Add a Popup — Show popups on markers
- Gesture Detector — Advanced gesture handling
- Multiple Markers — Markers with click handling
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.