Skip to main content

Google Maps Implementation

This page covers the Google Maps provider implementation. For the shared architecture, provider abstraction, and marker system, see the Maps overview.


GoogleMapContent​

GoogleMapContent uses the maps-compose library with MarkerComposable for Compose-native markers:

// ui/maps/GoogleMapContent.kt
GoogleMap(
cameraPositionState = cameraPositionState,
properties = MapProperties(
mapType = mapType,
isMyLocationEnabled = locationEnabled,
mapStyleOptions = mapStyleOptions, // Dark theme JSON style
),
uiSettings = MapUiSettings(
zoomControlsEnabled = false,
myLocationButtonEnabled = false,
compassEnabled = false,
mapToolbarEnabled = false,
),
) {
devices.forEach { deviceWithPosition ->
MarkerComposable(
keys = arrayOf(device.id, device.status, lat, lng, speed, course, isSelected),
state = MarkerState(position = LatLng(lat, lng)),
onClick = { onDeviceClick(deviceWithPosition); false },
) {
DeviceMarker(
variant = MarkerVariant.DEVICE,
device = device,
position = position,
isSelected = isSelected,
)
}
}
}

Key behaviors​

  • Dark theme: Loads R.raw.google_map_dark_style as MapStyleOptions when VislaTheme.isDarkTheme is true
  • Camera reporting: LaunchedEffect(cameraPositionState.position) maps the Google CameraPosition to MapCameraState
  • Fit-all logic: Uses LatLngBounds.builder() with smart detection β€” if all devices already fit in the visible region, it only pans to center without changing zoom
  • Projection: Exposes cameraPositionState.projection.toScreenLocation() for edge-clamped markers

Fit-All Algorithm​

private suspend fun fitAllDevices(devices: List<DeviceWithPosition>, cameraPositionState: CameraPositionState, forceZoom: Boolean = false) {
val positions = devices.mapNotNull { it.position }.filter { it.hasValidCoordinates }
if (positions.size >= 2) {
val bounds = LatLngBounds.builder().apply {
positions.forEach { include(LatLng(it.latitude!!, it.longitude!!)) }
}.build()

val visibleBounds = cameraPositionState.projection?.visibleRegion?.latLngBounds
val alreadyFits = !forceZoom && visibleBounds != null &&
visibleBounds.contains(bounds.northeast) &&
visibleBounds.contains(bounds.southwest)

if (alreadyFits) {
cameraPositionState.animate(CameraUpdateFactory.newLatLng(bounds.center))
} else {
cameraPositionState.animate(
CameraUpdateFactory.newLatLngBounds(bounds, MapConstants.FIT_ALL_PADDING_INT),
)
}
} else if (positions.size == 1) {
cameraPositionState.animate(
CameraUpdateFactory.newLatLngZoom(/* ... */, MapConstants.FOCUS_ZOOM_FLOAT),
)
}
}

Comparison with Mapbox​

For a side-by-side comparison of Google Maps and Mapbox features, see the Mapbox implementation.