Kameraposition auslesen
Im Controller von MapConductor gibt es keine getCameraPosition(). Das gilt für alle 3 Plattformen gleichermaßen. Da es sich um eine Methode handelt, die man bei der Arbeit mit der Karte als erstes sucht, schreibe ich hier auf, was man stattdessen verwendet und warum sie nicht bereitgestellt wird.
Kurz gesagt: Die Kamera ist etwas, das man nicht abholt, sondern das man empfängt. Die Karte-Site (SDK) kennt die Änderungen der Kamera, und wir müssen sie nur empfangen.
01 · 3 Wege
Nutzen Sie sie je nach Anwendungsfall. In allen Fällen kommt dasselbe MapCameraPosition an, und der sichtbare Bereich befindet sich in visibleRegion.
mapViewState.cameraPosition — jederzeit lesbar. Der sichtbare Bereich ist cameraPosition.visibleRegion.bounds.onCameraMove / onCameraMoveEnd — wird auch während der Bewegung ausgelöst. Wenn die UI folgen soll, nutzen Sie dies.onCameraChanged empfangen. So vermeiden Sie Konflikte um den einzelnen Listener-Slot des Karten-Controllers.val camera = mapViewState.cameraPosition
val bounds = camera.visibleRegion?.bounds
// Änderungen verfolgen
MapView(
state = mapViewState,
onCameraMoveEnd = { camera -> reload(camera.visibleRegion?.bounds) },
)let camera = mapViewState.cameraPosition
let bounds = camera.visibleRegion?.bounds
// Änderungen verfolgen
MapLibreMapView(state: mapViewState, onCameraMoveEnd: { camera in
reload(camera.visibleRegion?.bounds)
})const camera = mapViewState.cameraPosition;
const bounds = camera.visibleRegion?.bounds;
// Änderungen verfolgen
<MapLibreMapView
state={mapViewState}
onCameraMoveEnd={(camera) => reload(camera.visibleRegion?.bounds)}
/>02 · Warum es keine Get-Methode gibt
MapConductor basiert auf allen 3 Plattformen auf deklarativen UIs (Compose / SwiftUI / React). Bei deklarativen UIs gibt es genau einen Ort, der den „aktuellen Zustand“ hält, und die UI spiegelt ihn nur wider. Da auch die Kamera ein Zustand ist, gehört sie in den state.
Wenn man dem Controller Getter-Methoden hinzufügt, hat derselbe Wert zwei Ursprungsorte. Einerseits state, andererseits direktes Lesen aus dem SDK. Beide sollten gleich sein, aber es vergrößert nur die Möglichkeit einer Abweichung. Tatsächlich war das auch so; vor dem Entfernen baute ich im selben Frame zweimal die Kamera auf – einmal für das SDK-Event, einmal war es die Neuerstellung bei jedem Neuzeichnen.
Kameraereignis vom Karten-SDK
↓ der Anbieter liest die Rohwerte des SDK, rechnet in den einheitlichen Zoom um, hängt visibleRegion an
state.updateCameraPosition(camera)
↓
├─ mapViewState.cameraPosition (jederzeit lesbar)
├─ onCameraMove / onCameraMoveEnd (Callbacks der App)
└─ onCameraChanged registrierter Overlays (Erweiterungsmodule)03 · Die Kosten von Pull
Wenn man nur über Design spricht, neigt man dazu zu sagen „Man könnte ja noch eins hinzufügen“, also stelle ich Messwerte hier. Die Kamera einmal aufzubauen ist nicht billig.
Was bei einem einzigen Aufruf passiert
center / zoom / bearing / tilt aus dem SDK lesen → mit dem anbieterspezifischen ZoomAltitudeConverter in ein einheitliches Zoom konvertieren → die vier Bildschirmecken rückprojizieren und visibleRegion zusammenbauen. Der letzte Schritt ist der aufwendigste und kein einfacher Getter.
Messwerte. Ich habe die MapLibre-Markerseite in Chromium geöffnet und gezählt, wie oft getCameraPosition() bei einem Drag (ca. 20 Frames panning) aufgerufen wurde.
Es nahm um ca. 65 % ab. Die Abnahme ist alles Neuerstellung. Obwohl ich einmal im SDK-Event aufbaute und in den state überführte, baute die View bei jedem Neuzeichnen ein weiteres Mal denselben Wert auf. Der Aufrufer war in allen 13 Views vorhanden, das heißt, sie alle warfen den Wert weg, den sie gerade erst per push gesendet hatten.
Im Stillstand ist es übrigens 0 Mal. Es lief nicht außer Kontrolle, es war eine Kostenart, die während der Bedienung sicher zu tragen kommt. Da sie im schwersten Moment der Karte oben draufpackt, merkt man es leicht.
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 · Eine Frame-Verzögerung
mapViewState.cameraPosition ist „der zuletzt gepushte Wert“, also kann er rein logisch ein Frame älter sein als der aktuelle Wert des SDK. Das ist der einzige substanzielle Grund, einen Getter hinzuzufügen, also schreibe ich es ehrlich auf.
onCameraMove wird auch während der Bewegung in jedem Frame ausgelöst, daher hält state immer mit dem vorherigen Frame Schritt. Weder bei der Darstellung, beim Datenabruf noch bei der Bereichsprüfung gibt es spürbare Unterschiede. Tatsächlich laufen alle 41 Browsertests weiterhin durch, nachdem die Abrufmethoden entfernt wurden.
Rendering, der vollständig mit den Frames beim Ziehen synchronisiert ist. Da das jedoch an der Renderingschleife des Karten‑SDK hängt, sollte dies nicht über die einheitliche API, sondern über den im Folgenden beschriebenen Ausweg gelöst werden.
05 · Wenn man trotzdem Rohwerte braucht
Wenn über statе nicht ausgereicht ist – etwa um sich mit der Zeichenschleife des Karten-SDK zu synchronisieren –, dann greifen Sie über MapViewHolder direkt auf die native Karteninstanz zu, ohne den Umweg über die einheitliche API zu nehmen. Das ist anbieterspezifischer Code, aber dafür am schnellsten und am genauesten.
const holder = mapViewState.getMapViewHolder(); const map = holder?.map as maplibregl.Map | undefined; const liveZoom = map?.getZoom();