Leer la posición de la cámara
El controlador de MapConductor no tiene getCameraPosition(). Es igual en las 3 plataformas. Como es el primer método que se busca al tocar el mapa, escribo aquí qué usar en su lugar y por qué no se ha proporcionado.
En resumen, la cámara es algo que no se va a buscar, sino que se recibe. El lado del SDK del mapa sabe de los cambios de la cámara y a nosotros solo nos basta con recibirlos.
01 · 3 rutas
Úselas según el caso de uso. En todas llega el mismo MapCameraPosition y el área visible está en visibleRegion.
mapViewState.cameraPosition — se puede leer en cualquier momento. El área visible es cameraPosition.visibleRegion.bounds.onCameraMove / onCameraMoveEnd — se dispara también durante el movimiento. Si quieres que la UI siga, usa esto.onCameraChanged. Así no compites por el único slot de escucha del controlador del mapa.val camera = mapViewState.cameraPosition
val bounds = camera.visibleRegion?.bounds
// Sigue los cambios
MapView(
state = mapViewState,
onCameraMoveEnd = { camera -> reload(camera.visibleRegion?.bounds) },
)let camera = mapViewState.cameraPosition
let bounds = camera.visibleRegion?.bounds
// Sigue los cambios
MapLibreMapView(state: mapViewState, onCameraMoveEnd: { camera in
reload(camera.visibleRegion?.bounds)
})const camera = mapViewState.cameraPosition;
const bounds = camera.visibleRegion?.bounds;
// Sigue los cambios
<MapLibreMapView
state={mapViewState}
onCameraMoveEnd={(camera) => reload(camera.visibleRegion?.bounds)}
/>02 · Por qué no hay un método get
MapConductor se basa en las 3 plataformas en UI declarativa (Compose / SwiftUI / React). En la UI declarativa, el estado tiene un solo lugar que guarda "cómo está ahora" y la UI solo lo refleja. La cámara también es estado, así que su lugar es el state.
Si se agregan métodos de obtención al controlador, el mismo valor tendrá dos orígenes. Uno es el estado, el otro es la lectura directa del SDK. Ambos deberían ser iguales, pero solo aumenta la posibilidad de desviación. De hecho, sucedió así; antes de eliminarlo, estábamos armando la cámara dos veces en el mismo marco: una vez para el evento del SDK y la otra era la recreación en cada redibujado.
Evento de cámara del SDK de mapas
↓ el proveedor lee los valores crudos del SDK, los pasa al zoom unificado y adjunta visibleRegion
state.updateCameraPosition(camera)
↓
├─ mapViewState.cameraPosition (legible en cualquier momento)
├─ onCameraMove / onCameraMoveEnd (callbacks de la app)
└─ onCameraChanged de las capas registradas (módulos de extensión)03 · El costo de pull
Si se habla solo de diseño, se tiende a decir “podrías agregar uno más”, así que dejo aquí mediciones reales. Armar la cámara una vez no es barato.
Lo que se hace en una sola llamada
Leer center / zoom / bearing / tilt del SDK → convertir a un zoom unificado con ZoomAltitudeConverter específico del proveedor → retroyectar las cuatro esquinas de la pantalla y construir visibleRegion. El último es el más costoso y no es un simple getter.
Valores medidos. Abrí la página de marcadores de MapLibre en Chromium y conté cuántas veces se llamaba a getCameraPosition() en un solo arrastre (alrededor de 20 fotogramas de desplazamiento).
Se redujo aproximadamente un 65 %. Lo reducido es todo recreación. Aunque en el evento del SDK se armaba una vez y se enviaba al estado, en cada redibujado la vista volvía a armar el mismo valor. El llamador estaba en las 13 vistas, lo que significa que todas descartaban el valor que acababan de empujar poco antes.
Por cierto, en estado de reposo es 0 veces. No se estaba desbocando; era un tipo de costo que afecta con seguridad solo mientras se opera. Como se añade en el momento más pesado del mapa, es algo que se nota fácilmente.
var camera by remember { mutableStateOf<MapCameraPosition?>(null) }
MapLibreMapView(
state = mapViewState,
modifier = Modifier.weight(1f),
// Fires throughout the gesture — keep this handler cheap
onCameraMove = { position -> camera = position },
// Once, when the map settles. Fetching and redrawing belong here
onCameraMoveEnd = { position -> viewModel.onCameraSettled(position) },
)
camera?.let { position ->
Text("lat %.5f".format(position.position.latitude))
Text("lng %.5f".format(position.position.longitude))
Text("zoom %.2f".format(position.zoom))
// Read the visible region off the value you were handed; do not rebuild it
position.visibleRegion?.bounds?.let { bounds ->
Text("SW ${bounds.southWest} NE ${bounds.northEast}")
}
}04 · Un retraso de un fotograma
mapViewState.cameraPosition es “el último valor enviado por push”, así que teóricamente puede estar un fotograma más viejo que el valor actual del SDK. Este es el único motivo sustancial para agregar un método de obtención, así que lo escribo con honestidad.
onCameraMove se activa en cada frame incluso durante el movimiento, por lo que state siempre está al día con el frame inmediatamente anterior. No hay diferencias perceptibles en la visualización, obtención de datos ni evaluación de rangos. De hecho, tras eliminar los métodos de obtención, las 41 pruebas de navegador siguen pasando sin cambios.
Renderizado completamente sincronizado con los frames durante el arrastre. Sin embargo, dado que esto depende del ciclo de renderizado del SDK del mapa, es un ámbito donde se debe usar la vía de escape descrita más adelante, no la API unificada.
05 · Aun así, cuando se necesitan los valores brutos
Si a través de statе no es suficiente — por ejemplo, si quieres sincronizarte con el bucle de renderizado del SDK del mapa —, obtén la instancia nativa del mapa mediante MapViewHolder sin desviarte por la API unificada. Es código específico del proveedor, pero a cambio es lo más rápido y lo más preciso.
const holder = mapViewState.getMapViewHolder(); const map = holder?.map as maplibregl.Map | undefined; const liveZoom = map?.getZoom();