Control the camera
The camera decides which part of the map is on screen. A
CameraPosition has a center position, a zoom level, a bearing, and
a pitch. At zoom 0 the whole world is 512 dp wide, and each zoom level doubles
the scale. Bearing is the compass direction at the top of the screen, in
degrees clockwise from north. Pitch tilts the map, in degrees away from looking
straight down.
Set the starting position
Section titled “Set the starting position”Pass initialCameraPosition to rememberMapState:
val mapState = rememberMapState( initialCameraPosition = CameraPosition(center = Position(latitude = 45.521, longitude = -122.675), zoom = 13.0) )MaplibreMap(state = mapState)The map starts at this position. Later changes to initialCameraPosition do not
move the map; use the functions below instead. The map state saves the camera,
so the map comes back to the same place after an Android activity is recreated.
Read the current camera from MapState.cameraPosition. A composable
that reads it recomposes when the camera moves.
Animate the camera
Section titled “Animate the camera”MapState.animateCamera moves the camera smoothly. It is a suspend
function, so call it from a coroutine. List only the properties you want to
change in a CameraUpdate; the others keep their current values:
val scope = rememberCoroutineScope()Button( onClick = { scope.launch { mapState.animateCamera(CameraUpdate(zoom = mapState.cameraPosition.zoom + 1.0)) } }) { Text("Zoom in")}If the map is not on screen yet, the call waits until it is, so you can also
start an animation from LaunchedEffect when the screen opens.
Fly or ease
Section titled “Fly or ease”The animation parameter chooses how the camera travels.
CameraAnimation.Ease is the default. It moves straight to the
target in 300 ms. Zoom changes directly from the current level to the target
level. Use it for short moves, such as zooming in place or following a moving
point. Over long distances the map moves too fast to follow.
CameraAnimation.Fly zooms out, travels, and zooms back in, so the
user can see where the camera is going. Use it to move between places that are
far apart, such as two cities:
LaunchedEffect(mapState, city) { mapState.animateCamera( update = CameraUpdate(center = city, zoom = 12.0), animation = CameraAnimation.Fly { duration = 3.seconds }, )}Without a duration, the flight takes longer for longer distances. Set speed
to change how fast it travels. To change the length of an ease, set its
duration in the same way: CameraAnimation.Ease { duration = 1.seconds }.
Move without animation
Section titled “Move without animation”MapState.setCameraPosition moves the camera at once. It replaces
every camera property, so copy the current position and change only what you
need:
fun showPlace(mapState: MapState, place: Position) { mapState.setCameraPosition(mapState.cameraPosition.copy(center = place, zoom = 15.0))}You can call it before the map is on screen. The map then starts at that position.
Zoom around a point
Section titled “Zoom around a point”MapState.animateCameraAround changes zoom, bearing, or pitch while
one point stays at the same place on screen. For example, zoom in on a place the
user selected without moving that place on screen:
suspend fun zoomInOn(mapState: MapState, place: Position) { mapState.animateCameraAround( anchor = CameraAnchor.Geographic(place), zoom = mapState.cameraPosition.zoom + 2.0, )}CameraAnchor.Geographic keeps a geographic position in place.
CameraAnchor.Screen keeps a screen point in place, in dp from the
map’s top-left corner. The point must be visible when the animation starts.
The point under the cursor stays in place.
Fit an area on screen
Section titled “Fit an area on screen”MapState.animateCameraToBounds moves the camera so a
BoundingBox fits on screen. Use it to show a whole route or a set of search
results. By default it flies.
suspend fun showRoute(mapState: MapState, routeBounds: BoundingBox) { mapState.animateCameraToBounds( boundingBox = routeBounds, fit = CameraFit(fitPadding = DpPadding(left = 32.dp, top = 32.dp, right = 32.dp, bottom = 32.dp)), )}fitPadding in CameraFit keeps space between the box and the
edges of the map. The fit also stays clear of the viewportInsets that you pass
to MaplibreMap, such as the area under a bottom sheet; see
Keep controls clear of your UI. To fit
without animation, call MapState.fitCameraToBounds.
MapState.cameraForBounds calculates the fitted camera without
moving the map, so you can change it before you apply it. For example, cap the
zoom so that a fit around a few nearby points does not zoom in too far:
suspend fun showResults(mapState: MapState, resultBounds: BoundingBox) { val camera = mapState.cameraForBounds(resultBounds) mapState.animateCamera( update = camera.copy(zoom = minOf(camera.zoom, 15.0)).toCameraUpdate(), animation = CameraAnimation.Fly.Standard, )}To fit a list of points or a line instead of a box, use
MapState.cameraForCoordinates or
MapState.cameraForGeometry.
Read the visible area
Section titled “Read the visible area”MapState.viewport describes what the map shows on screen: the
camera, the map’s size, and the geographic area in view. Use it to load data for
the visible area or to show the current zoom:
val viewport = mapState.viewportif (viewport != null) { Text("Zoom: ${viewport.cameraPosition.zoom}") Text("Visible bounds: ${viewport.visibleBounds}")}The viewport is null until the map draws for the first time. A composable that
reads it recomposes when the camera moves or the map changes size.
The map repeats the world to the left and right, so longitudes in
Viewport.visibleBounds can go past ±180°. Call
VisibleBounds.toBoundingBox to get a standard GeoJSON bounding box.
Convert between screen and map coordinates
Section titled “Convert between screen and map coordinates”MapState.screenLocationFromPosition finds where a geographic
position appears on screen.
MapState.positionFromScreenLocation finds the geographic position
under a screen point. Screen points are in dp from the map’s top-left corner:
val centerOnScreen: DpOffset? = mapState.screenLocationFromPosition(mapState.cameraPosition.center)val positionAtPoint: Position? = mapState.positionFromScreenLocation(DpOffset(x = 100.dp, y = 150.dp))Both return null until the map draws for the first time. To place Compose UI
at a geographic position, use MapOverlayScope.placedAt from
Overlay Compose UI instead.
Limit where the camera can go
Section titled “Limit where the camera can go”Pass CameraConstraints to MaplibreMap to keep the
camera inside limits. Use it to keep users in the area your app covers, or to
stop them zooming past the detail your map data has:
MaplibreMap( state = mapState, cameraConstraints = CameraConstraints { minZoom = 10.0 maxZoom = 18.0 maxPitch = 0.0 boundingBox = BoundingBox(west = -122.84, south = 45.43, east = -122.47, north = 45.65) },)Properties you leave out keep their defaults: zoom 0 to 20 and pitch 0 to 60.
Setting maxPitch to 0 keeps the map flat. The limits apply to gestures and to
camera movements from your code. boundingBox stops the user from panning away
from that area.