Skip to content

MapCameraPositionInterface (Camera Position)

MapCameraPositionInterface represents the camera’s view position, orientation, and visible region on the map. It defines where the camera is looking, how much of the map is visible, and the viewpoint used to display the map.

Interface and Implementation

MapCameraPositionInterface Interface

interface MapCameraPositionInterface {
val position: GeoPointInterface
val zoom: Double
val bearing: Double
val tilt: Double
val paddings: MapPaddingsInterface?
val visibleRegion: VisibleRegion?
}

MapCameraPosition

The main implementation provides immutable camera position data:

data class MapCameraPosition(
override val position: GeoPoint,
override val zoom: Double = 0.0,
override val bearing: Double = 0.0,
override val tilt: Double = 0.0,
override val paddings: MapPaddingsInterface? = MapPaddings.Zeros,
override val visibleRegion: VisibleRegion? = null
) : MapCameraPositionInterface

Properties

Camera Position

  • position: GeoPointInterface: The geographic center point of the camera view
  • zoom: Double: The zoom level (approximately follows the Google Maps scale)
  • bearing: Double: The compass direction in degrees (0 = north, 90 = east)
  • tilt: Double: The camera tilt angle in degrees (0 = straight down, 90 = horizontal)

View Configuration

  • paddings: MapPaddingsInterface?: Viewport padding that affects the visible region
  • visibleRegion: VisibleRegion?: (read-only) The geographic bounds actually displayed on screen

Creation Methods

Default Position

// Default camera position at the origin
val defaultPosition = MapCameraPosition.Default

Custom Position

// Basic camera position
val sanFrancisco = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
zoom = 15
)
// Camera with bearing and tilt
val aerialView = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
zoom = 18,
bearing = 45, // Northeast direction
tilt = 60 // Angled view
)

Zoom Levels

MapConductor zoom levels approximately follow the Google Maps scale, but they may vary slightly between map SDKs:

  • 0-2: World view, continents are visible
  • 3-5: Country level
  • 6-9: State/region level
  • 10-12: City level
  • 13-15: District/neighborhood level
  • 16-18: Street level
  • 19-21: Building level (high detail)
// Different zoom levels for different use cases
val worldView = MapCameraPosition(
position = GeoPoint.fromLatLong(0, 0),
zoom = 2 // Show continents
)
val cityView = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
zoom = 12 // Show the entire city
)
val streetView = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
zoom = 17 // Show individual streets
)

Bearing and Tilt

Bearing (Rotation)

Bearing rotates the map around the center point:

// North up (default)
val northUp = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
bearing = 0
)
// East up
val eastUp = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
bearing = 90
)
// Follow the route bearing
val routeBearing = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
bearing = 135, // Southeast
zoom = 18.0
)

Tilt (3D Viewpoint)

Tilt provides a 3D viewing angle:

// Top-down view (default)
val topDown = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
tilt = 0
)
// Tilted view for depth
val angled = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
tilt = 30,
zoom = 16
)
// Maximum street-level tilt
val streetLevel = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
tilt = 80,
bearing = 45,
zoom = 19
)

Negative tilt Values - Looking Forward

When tilt is set to a negative value, the camera uses an upward-looking elevation view above the horizon. Positive tilt is treated as a view looking down at the ground, while negative tilt is treated as a view looking forward in the bearing direction.

// 通常の俯瞰ビュー(従来の動作)
MapCameraPosition(
position = GeoPoint(35.68, 139.76),
zoom = 15.0,
tilt = 60.0, // 地面を60度の角度で見下ろす
)
// 前方(水平線上方)を見る
MapCameraPosition(
position = GeoPoint(35.68, 139.76),
zoom = 15.0,
tilt = -30.0, // 水平線より30度上方を見る
bearing = 45.0,
)
// ArcGIS はネイティブ対応
  • ArcGIS: ArcGIS のカメラ仕様と同じく、負の tilt にネイティブ対応します。
  • Google Maps / Mapbox / MapLibre / HERE: 上向きピッチを直接表現できないため、カメラ位置を固定し、bearing 方向の前方へターゲットを移動してシミュレーションします。ネイティブの仰角ビューと完全に同一にはなりません。

Visible Region

The visible region describes the actual geographic area displayed on screen after accounting for the camera position, zoom level, bearing, tilt, and viewport padding.

VisibleRegion Class

data class VisibleRegion(
val bounds: GeoRectBounds, // Overall bounding rectangle
val nearLeft: GeoPointInterface?, // Lower-left corner (side closest to the camera)
val nearRight: GeoPointInterface?, // Lower-right corner (side closest to the camera)
val farLeft: GeoPointInterface?, // Upper-left corner (side farthest from the camera)
val farRight: GeoPointInterface? // Upper-right corner (side farthest from the camera)
)

Properties

  • bounds: GeoRectBounds: Geographic bounds of the rectangle that contains the entire visible region. This is the smallest bounding rectangle that contains all visible content.
  • nearLeft: GeoPointInterface?: Geographic coordinates of the lower-left corner of the visible region. “near” refers to the side closest to the camera position.
  • nearRight: GeoPointInterface?: Geographic coordinates of the lower-right corner of the visible region.
  • farLeft: GeoPointInterface?: Geographic coordinates of the upper-left corner of the visible region. “far” refers to the side farthest from the camera position.
  • farRight: GeoPointInterface?: Geographic coordinates of the upper-right corner of the visible region.

Using VisibleRegion

@Composable
fun VisibleRegionExample() {
val mapViewState = rememberMapboxMapViewState(
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
zoom = 15.0
),
)
var visibleRegionInfo by remember { mutableStateOf<VisibleRegion?>(null) }
// Replace MapView with your chosen map SDK, such as GoogleMapView or MapboxMapView
MapboxMapView(
state = mapViewState,
modifier = modifier,
onCameraMoveEnd = { cameraPosition ->
cameraPosition.visibleRegion?.let { visibleRegion ->
visibleRegionInfo = visibleRegion
}
},
) {
// Display visible region information
visibleRegionInfo?.let { region ->
// Show the bounds as a polygon
region.bounds.let { bounds ->
if (!bounds.isEmpty) {
val sw = bounds.southWest!!
val ne = bounds.northEast!!
Polygon(
points = listOf(
sw,
GeoPoint.fromLatLong(sw.latitude, ne.longitude),
ne,
GeoPoint.fromLatLong(ne.latitude, sw.longitude),
sw
),
strokeColor = Color.Blue,
strokeWidth = 2.dp,
fillColor = Color.Blue.copy(alpha = 0.1f)
)
}
}
}
}
}

Animations and Transitions

Smooth Camera Movement

@Composable
fun AnimatedCameraExample() {
val locations = listOf(
GeoPoint.fromLatLong(37.7749, -122.4194), // San Francisco
GeoPoint.fromLatLong(40.7128, -74.0060), // New York
GeoPoint.fromLatLong(51.5074, -0.1278) // London
)
val mapViewState = rememberHereMapViewState(
cameraPosition = MapCameraPosition(
position = locations[0],
zoom = 6.0
),
)
var currentIndex by remember { mutableStateOf(0) }
// Animate to the next location every 5 seconds
LaunchedEffect(Unit) {
while (true) {
delay(5000)
currentIndex = (currentIndex + 1) % locations.size
val targetPosition = MapCameraPosition(
position = locations[currentIndex],
zoom = 6.0,
bearing = 0.0,
tilt = 0.0
)
mapViewState.moveCameraTo(targetPosition, 1000)
}
}
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView
HereMapView(
state = mapViewState,
modifier = modifier,
) {
// Add markers for each location
locations.forEachIndexed { index, location ->
Marker(
position = location,
icon = ColorDefaultIcon(
fillColor = if (index == currentIndex) Color.Red else Color.Gray,
label = when (index) {
0 -> "SF"
1 -> "NYC"
2 -> "LON"
else -> "$index"
}
)
)
}
}
}

Interactive Camera Control

@Composable
fun CameraControlExample(modifier: Modifier = Modifier) {
val mapViewState = rememberMapLibreMapViewState(
mapDesign = MapLibreDesignType(
id = "debug-tiles",
styleJsonURL = "https://demotiles.maplibre.org/debug-tiles/style.json",
),
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLatLong(37.7749, -122.4194),
zoom = 15.0
),
)
Column(
modifier = modifier,
) {
// Camera controls
Row {
Button(
onClick = {
val newZoom = mapViewState.cameraPosition.copy(
zoom = (mapViewState.cameraPosition.zoom + 1)
.coerceAtMost(21.0),
)
mapViewState.moveCameraTo(newZoom, 500)
}
) {
Text("Zoom In")
}
Button(
onClick = {
val newZoom = mapViewState.cameraPosition.copy(
zoom = (mapViewState.cameraPosition.zoom - 1)
.coerceAtMost(21.0),
)
mapViewState.moveCameraTo(newZoom, 500)
}
) {
Text("Zoom Out")
}
Button(
onClick = {
val newZoom = mapViewState.cameraPosition.copy(
bearing = (mapViewState.cameraPosition.bearing + 45) % 360,
)
mapViewState.moveCameraTo(newZoom, 500)
}
) {
Text("Rotate")
}
}
// Tilt slider
Slider(
value = mapViewState.cameraPosition.tilt.toFloat(),
onValueChange = { tilt ->
val newZoom = mapViewState.cameraPosition.copy(
tilt = tilt.toDouble(),
)
mapViewState.moveCameraTo(newZoom)
},
valueRange = 0f..80f
)
// Replace MapView with your chosen map SDK, such as GoogleMapView or MapboxMapView
MapLibreMapView(
state = mapViewState,
) {
Marker(
position = mapViewState.cameraPosition.position,
icon = ColorDefaultIcon(fillColor = Color.Red)
)
}
}
}

Fitting Bounds

Use fitBounds to move and zoom the camera so that a GeoRectBounds is fully visible:

val bounds = GeoRectBounds(
southWest = GeoPoint.fromLatLong(37.7649, -122.4294),
northEast = GeoPoint.fromLatLong(37.7849, -122.4094)
)
mapViewState.fitBounds(bounds, padding = 32)

The padding parameter adds screen-space inset in pixels on all sides. Each provider calculates the resulting zoom level natively.

Showing Multiple Points

To frame a set of markers or route points, extend a bounds and then fit:

// Calculate bounds containing all points, then set appropriate zoom
val allPoints = listOf(/* your points */)
val bounds = GeoRectBounds()
allPoints.forEach { bounds.extend(it) }
val centerCamera = MapCameraPosition(
position = bounds.center ?: GeoPoint.fromLatLong(0.0, 0.0),
zoom = calculateZoomForBounds(bounds), // Use fitBounds instead of manual zoom calculation
bearing = 0.0,
tilt = 0.0
)

The fitBounds call replaces the manual zoom calculation shown above:

val bounds = GeoRectBounds()
allPoints.forEach { bounds.extend(it) }
if (!bounds.isEmpty) {
mapViewState.fitBounds(bounds, padding = 48)
}