Skip to content
This site is a preview of pull request #1363.

Handle map input and clicks

Users can pan, zoom, rotate, and tilt the map by default. Two builders configure input. MapInteractions sets which camera movements are allowed and how your app responds to clicks. MapUiOptions sets what gestures, scrolling, and keys do.

For a map that users can pan and zoom but cannot rotate or tilt, disable those camera movements:

App.kt
MaplibreMap(
interactions =
MapInteractions {
camera {
rotate { enabled = false }
tilt { enabled = false }
}
}
)

Omitted settings keep their defaults. Use MapInteractions(from = existing) to build on another configuration. MapInteractions.None disallows every camera movement. MapUiOptions.None disables every gesture, scroll, and key binding, including feature clicks.

To stop following the user’s location when they pan, use camera.pan.onStart. This hook runs before the component responds to input and can run again within the same gesture. It does not report when camera movement ends. For moving the camera from app code, see Control the camera.

Vertical scrolling zooms by default. This example makes scrolling pan the map, while Ctrl-scroll zooms:

App.kt
MaplibreMap(
uiOptions =
MapUiOptions {
bindings {
scroll {
mappings {
on(modifiers = Containing(KeyModifier.Ctrl), response = ScrollResponse.Zoom)
otherwise(ScrollResponse.Pan)
}
}
}
}
)

A mappings block replaces the default responses for that input. Rows are tried from top to bottom; otherwise handles the remaining input. Available gestures depend on the input device and platform. A mapping cannot enable a camera movement that MapInteractions disallows.

A null pointer-type, button, or modifier filter matches any value. Key mappings default to exactly no modifiers; set modifiers = null to match a key with any modifiers.

Use callbacks.click to act on a geographic location. For example, pass the clicked position to your app:

App.kt
@Composable
fun ClickableMap(onLocationSelected: (Position) -> Unit) {
MaplibreMap(
interactions =
MapInteractions {
callbacks {
click {
onEvent { event ->
event.position?.let(onLocationSelected)
ClickResult.Consume
}
}
}
}
)
}

Return ClickResult.Consume when your app handles the click, or ClickResult.Pass to let it continue to interactive layers. Use longClick for a long press or secondary mouse click.

A layer’s onClick receives the features under the pointer. Use hitPadding to make small features easier to select:

App.kt
val state = rememberMapState {
val interactiveAmtrakStations =
rememberGeoJsonSource(GeoJsonData.Uri(Res.getUri("files/data/amtrak_stations.geojson")))
CircleLayer(
id = "amtrak-stations",
source = interactiveAmtrakStations,
hitPadding = 12.dp,
onClick = { features ->
println("Clicked on ${features[0].toJson()}")
ClickResult.Consume
},
)
}
MaplibreMap(state = state)

Layers receive clicks from front to back until a handler returns ClickResult.Consume. Use callbacks.click.onUnhandled for clicks that no layer handles, such as clearing an app’s selection.

Code that recognizes gestures itself can pass them through MapState.panBy, MapState.scaleBy, MapState.fling, and MapState.click. They follow the camera permissions and callbacks in MapInteractions and report CameraMoveReason.GESTURE, like a pointer gesture. They take logical pixels and do nothing while no map is presented. See Present on an Android Surface.

Use camera.rotate.snapping to snap to nearby bearings after rotation ends. Choose specific bearings or evenly spaced targets, such as the four cardinal directions:

MaplibreMap(
interactions =
MapInteractions {
camera {
rotate {
snapping {
targets = BearingTargets.evenlySpaced(count = 4)
tolerance = 7.0
}
}
}
}
)

Use camera.rotate.haptics to add feedback at chosen bearings while rotating. Haptics work independently of snapping and support Android, iOS, and macOS. Combine notches with different emphasis levels:

MaplibreMap(
interactions =
MapInteractions {
camera {
rotate {
haptics {
notch(BearingTargets.evenlySpaced(24), HapticEmphasis.Subtle)
notch(BearingTargets.evenlySpaced(4), HapticEmphasis.Standard)
notch(BearingTargets.at(0.0), HapticEmphasis.Emphasized)
}
}
}
}
)