Kamera — der schwierigste Teil der Abstraktion
Bei MapConductor ist die Vereinheitlichung der Kamera am schwierigsten und wertvollsten. Ein Kameramodell, das aus position, zoom, bearing, tilt und visibleRegion besteht, wird in mehr als 10 Karten-Engines mit derselben Bedeutung vereinheitlicht. Wenn Sie dieselbe MapCameraPosition übergeben, erhalten Sie bei jedem Anbieter dieselbe Ansicht.
01 · Warum die Vereinheitlichung sinnvoll ist
Jede Karten-Engine verfügt über ein eigenes Kameramodell. Manche sprechen von Zoom, manche von Kamerahöhe, manche drehen sich um einen Bodenfokuspunkt, andere bewegen den Blickpunkt selbst. Wenn Sie das native SDK direkt verwenden, führt das Austauschen des Anbieters sofort zu „gleiche Zahl, aber andere Ansicht“, und Sie müssen sogar die Bildschirmlogik umschreiben.
MapConductor kapselt diesen Unterschied in einer Adapterebene und zeigt der App nur ein einziges Kameramodell. Dadurch wird die Kameraposition zu einem wert, das über Anbieter hinweg transportiert werden kann. Auf der Seite Camera Sync der Beispiel-App können Sie tatsächlich sehen, wie eine Kameraposition an alle Anbieter weitergegeben wird und dieselbe Stelle, derselbe Maßstab und dieselbe Neigung übereinstimmen.
Zoom oder Höhe
2D-Kachel-Systeme drücken den Maßstab durch die Zoomstufe aus, 3D-Systeme durch Kamerahöhe oder Sichtweite. Auch die Einheiten und Bezugsgrößen sind unterschiedlich.
Blickpunkt oder Blickrichtung
Ein Modell, das sich um einen Punkt auf der Erde dreht (Orbit), und ein Modell, das die Position und Ausrichtung der Kamera selbst beschreibt, existieren nebeneinander.
Zulässiger Neigungsbereich
Der maximale Neigungswinkel und die Möglichkeit, nach oben zu neigen, unterscheiden sich je nach Engine. Keiner unterstützt eine negative Neigung.
val camera = MapCameraPosition(
position = geoPoint,
zoom = 18.0,
tilt = 70.0,
)
moveCameraTo(camera)02 · Vereinheitlichtes Kameramodell
Die Kamera wird durch ein einziges MapCameraPosition dargestellt. Es gibt keinen anbieterspezifischen Typ; Sie erstellen nur diesen Wert und übergeben ihn an moveCameraTo.
Mit copy() können nur Teile ausgetauscht werden; Vergleiche werden mit einer Toleranz von Zoom 0,01 und Neigung 0,01 durchgeführt. Dies verhindert, dass geringfügige Schwankungen der vom Anbieter zurückgegebenen Werte fälschlicherweise als „bewegt“ interpretiert werden.
03 · Vereinheitlichung von Zoomlevel = Höhe
Der Zoom wird intern genauso behandelt wie die „Kamerahöhe“. Im `core` gibt es Konverter, die 2D-Tile-Zoom und 3D-Höhen/-Sichtweiten gegenseitig umwandeln können; die Werte aller Provider werden zunächst in einen gemeinsamen Zoom (Google-Maps-Standard) umgerechnet, bevor sie veröffentlicht werden.
Dieser Standard wurde nicht nur logisch festgelegt. Mit dem Kalibrierungstool der Beispiel-App wurde der tatsächlich sichtbare Bereich für jeden Motor gemessen, wenn derselbe Zoom angegeben wurde, und die Standardhöhe so kalibriert, dass die Flächen übereinstimmen. Deshalb ist Zoom 14 bei allen Providern gleich breit.
Derselbe Zoom braucht je nach Breitengrad eine andere Höhe
Web-Mercator vergrößert die Karte zu den Polen hin, deshalb unterscheidet sich die Kamerahöhe für eine Zoomstufe zwischen Äquator und hohen Breiten um ein Mehrfaches. Die Umrechnung Zoom ⇄ Höhe berücksichtigt das.
Konstant sind nur die Beziehung „ein Schritt pro Verdopplung“ und die Höhe von Zoom 0 als Referenz. Auch die Tatsache, dass die Bodendistanz bei gleichem Zoom mit höherer Breite kleiner wird, ist in die Umrechnung einbezogen, sodass sich der Maßstab zwischen Tokio und Honolulu nicht verschiebt.
04 · Pseudo-Unterstützung für tilt < 0
tilt akzeptiert Werte von −60 bis 60 Grad. Positive Werte sind die gewohnte schiefe Aufsicht, negative Werte sind eine Blickrichtung nach oben über den Horizont. Kein Kartenmotor kann dies direkt ausdrücken; alle SDKs akzeptieren für pitch nur Werte ≥ 0.
MapConductor ersetzt dies daher so, dass Position und Ausrichtung der Kamera erhalten bleiben, dem Motor aber ein positiver Neigungswert übergeben wird. Für die App kann negativer tilt direkt verwendet werden, und die abgerufene Kameraposition wird ebenfalls als negativer tilt zurückgegeben.
Ein Grundriss, betrachtet von direkt oben. Ein Rechteck auf der Erdoberfläche wird direkt zu einem Rechteck auf dem Bildschirm.
Eine schräge Ansicht, nach vorne geneigt. Auf der Erdoberfläche entsteht ein Trapez, das vorne schmaler und hinten breiter ist.
Eine Aufsicht über den Horizont. Eine Perspektive, die den Himmel einschließt, und die nativ nicht existiert.
Die einzige Ausnahme ist Google Maps 2D auf React — die Rasterkarte der JavaScript-API. Ihre Neigung hängt von Browser- und Kachelbedingungen ab, sodass Höhe und Ausrichtung, auf denen die Nachbildung beruht, nicht steuerbar sind. Google Maps 3D (Map3DElement) unterstützt negativen Tilt.
// Den Regler von −60 bis 60 einfach durchreichen; ein negativer Wert ist derselbe Aufruf currentPosition = currentPosition.copy(tilt = tilt) mapViewState.value?.moveCameraTo(currentPosition)
func setTilt(_ angle: Double, state: any MapViewStateProtocol) {
currentPosition = currentPosition.copy(tilt: angle)
state.moveCameraTo(cameraPosition: currentPosition)
}<SliderControl
label="Tilt" value={tilt} min={-60} max={60} step={1}
onChange={value => {
setTilt(value);
const next = cameraPositionRef.current.copy({ tilt: value });
cameraPositionRef.current = next;
mapViewState.moveCameraTo(next, 400);
}}
/>05 · VisibleRegion ― der tatsächlich sichtbare Bereich
Wenn die Kamera geneigt wird, wird das Bildschirmrechteck am Boden zu einem Trapez. Ein Rechteck, das nur Südwest und Nordost umfasst (bounds), kann den Unterschied im Bereich zwischen Vorder- und Hintergrund nicht ausdrücken. VisibleRegion enthält zusätzlich zu dem Rechteck die Bodenkoordinaten der vier Bildecken, sodass auch mit geneigter Kamera „der tatsächlich abgebildete Bereich“ behandelt werden kann.
near ist die untere Bildschirmkante, also die der Kamera nähere Seite; far ist die obere Kante, die entferntere Seite. left / right beziehen sich auf den Bildschirm, daher ändert sich ihre Bedeutung nicht, wenn die Karte mit bearing gedreht wird. Dank dieser vier Punkte können auch bei geneigter Karte Datenabrufbereich und Hinweise auf Quellen korrekt bestimmt werden.
mapViewState.cameraPosition.visibleRegion?.let { region ->
val sw = region.bounds.southWest
val ne = region.bounds.northEast
// Die vier Ecken eines geneigten Bildschirms — als Trapez nutzbar
val corners = listOf(region.nearLeft, region.nearRight, region.farLeft, region.farRight)
}if let region = cameraPosition.visibleRegion {
let bounds = region.bounds
let corners = [region.nearLeft, region.nearRight, region.farLeft, region.farRight]
}<MapViewContainer initialCamera={INIT_CAMERA} onCameraMove={setCameraPosition}>
{/* Den Inhalt von cameraPosition.visibleRegion auflisten */}
</MapViewContainer>
const visibleRegion = cameraPosition?.visibleRegion ?? null;
visibleRegion?.nearLeft?.toUrlValue(5);
visibleRegion?.farRight?.toUrlValue(5);