it-swarm.com.de

Google Maps V3 - Berechnen der Zoomstufe für bestimmte Grenzen

Ich suche nach einer Möglichkeit, die Zoomstufe für bestimmte Grenzen mithilfe der Google Maps V3-API zu berechnen, ähnlich wie getBoundsZoomLevel() in der V2-API.

Folgendes möchte ich tun:

// These are exact bounds previously captured from the map object
var sw = new google.maps.LatLng(42.763479, -84.338918);
var ne = new google.maps.LatLng(42.679488, -84.524313);
var bounds = new google.maps.LatLngBounds(sw, ne);
var zoom = // do some magic to calculate the zoom level

// Set the map to these exact bounds
map.setCenter(bounds.getCenter());
map.setZoom(zoom);

// NOTE: fitBounds() will not work

Leider kann ich die fitBounds()-Methode nicht für meinen speziellen Anwendungsfall verwenden. Es eignet sich gut für das Anpassen von Markierungen auf der Karte, aber nicht für das Setzen von genauen Grenzen. Hier ist ein Beispiel, warum ich die fitBounds()-Methode nicht verwenden kann.

map.fitBounds(map.getBounds()); // not what you expect
124
Nick Clark

Eine ähnliche Frage wurde in der Google-Gruppe gestellt: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

Die Zoomstufen sind diskret, wobei sich die Skala in jedem Schritt verdoppelt. Daher können Sie im Allgemeinen nicht genau die gewünschten Grenzen festlegen (es sei denn, Sie haben mit der Kartengröße sehr viel Glück).

Ein anderes Problem ist das Verhältnis zwischen Seitenlängen, z. Sie können die Begrenzungen nicht genau an ein dünnes Rechteck in einer quadratischen Karte anpassen.

Es gibt keine einfache Antwort darauf, wie Sie die exakten Grenzen anpassen, denn selbst wenn Sie die Größe des Kartendatums ändern möchten, müssen Sie die Größe und die entsprechende Zoomstufe auswählen, die Sie ändern (grob gesagt, vergrößern oder verkleinern) als es derzeit ist?).

Wenn Sie den Zoom wirklich berechnen müssen, anstatt ihn zu speichern, sollte dies den Trick erfüllen:

Die Mercator-Projektion verzerrt den Breitengrad, aber jeder Längenunterschied repräsentiert immer den gleichen Bruchteil der Breite der Karte (der Winkelunterschied in Grad/360). Bei Zoom Null ist die gesamte Weltkarte 256 x 256 Pixel, und durch das Zoomen jeder Ebene werden Breite und Höhe verdoppelt. Nach einer kleinen Algebra können wir den Zoom wie folgt berechnen, vorausgesetzt, wir kennen die Breite der Karte in Pixel. Beachten Sie, dass der Längengrad positiv ist, da sich der Längengrad umläuft.

var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = sw.lng();
var east = ne.lng();
var angle = east - west;
if (angle < 0) {
  angle += 360;
}
var zoom = Math.round(Math.log(pixelWidth * 360 / angle / GLOBE_WIDTH) / Math.LN2);
98
Giles Gardam

Vielen Dank an Giles Gardam für seine Antwort, die jedoch nur Längen- und nicht Breitengrade anspricht. Eine vollständige Lösung sollte die Zoomstufe berechnen, die für den Breitengrad und die Zoomstufe für den Längengrad benötigt wird, und dann die kleinere (weiter entfernte) von beiden nehmen.

Hier ist eine Funktion, die sowohl den Breitengrad als auch den Längengrad verwendet:

function getBoundsZoomLevel(bounds, mapDim) {
    var WORLD_DIM = { height: 256, width: 256 };
    var ZOOM_MAX = 21;

    function latRad(lat) {
        var sin = Math.sin(lat * Math.PI / 180);
        var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
        return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
    }

    function zoom(mapPx, worldPx, fraction) {
        return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
    }

    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();

    var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;

    var lngDiff = ne.lng() - sw.lng();
    var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;

    var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
    var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);

    return Math.min(latZoom, lngZoom, ZOOM_MAX);
}

Demo on jsfiddle

Parameter:

Der Parameterwert "bounds" sollte ein google.maps.LatLngBounds-Objekt sein.

Der Parameterwert "mapDim" sollte ein Objekt mit den Eigenschaften "height" und "width" sein, die die Höhe und Breite des DOM-Elements darstellen, das die Karte anzeigt. Sie können diese Werte verringern, wenn Sie das Auffüllen sicherstellen möchten. Das heißt, Sie möchten möglicherweise nicht, dass sich die Kartenmarkierungen innerhalb der Grenzen zu nahe am Kartenrand befinden.

Wenn Sie die jQuery-Bibliothek verwenden, kann der Wert mapDim wie folgt abgerufen werden:

var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };

Wenn Sie die Prototype-Bibliothek verwenden, kann der mapDim-Wert wie folgt ermittelt werden:

var mapDim = $('mapElementId').getDimensions();

Rückgabewert:

Der Rückgabewert ist die maximale Zoomstufe, die weiterhin die gesamten Grenzen anzeigt. Dieser Wert liegt zwischen 0 und der maximalen Zoomstufe (einschließlich).

Die maximale Zoomstufe ist 21. (Ich glaube, es war nur 19 für Google Maps API v2.)


Erläuterung:

Google Maps verwendet eine Mercator-Projektion. In einer Mercator-Projektion sind die Längengradlinien gleich beabstandet, die Breitengradlinien jedoch nicht. Der Abstand zwischen den Breitengraden nimmt vom Äquator zu den Polen zu. Tatsächlich tendiert die Entfernung zur Unendlichkeit, wenn sie die Pole erreicht. Auf einer Google Maps-Karte werden jedoch keine Breiten über ungefähr 85 Grad Nord oder unter ungefähr -85 Grad Süd angezeigt. ( Referenz ) (Ich berechne den tatsächlichen Cutoff bei +/- 85.05112877980658 Grad.)

Dies macht die Berechnung der Brüche für die Grenzen für die Breite komplizierter als für die Länge. Ich habe eine Formel aus Wikipedia verwendet, um den Breitengradbruch zu berechnen. Ich gehe davon aus, dass dies mit der von Google Maps verwendeten Projektion übereinstimmt. Schließlich enthält die Google Maps-Dokumentationsseite, auf die ich oben verweise, einen Link zu derselben Wikipedia-Seite.

Weitere Hinweise:

  1. Die Zoomstufen reichen von 0 bis zur maximalen Zoomstufe. Zoomstufe 0 ist die Karte, die vollständig herausgezoomt ist. Höhere Ebenen vergrößern die Karte weiter. ( Referenz )
  2. Bei Zoomstufe 0 kann die gesamte Welt in einem Bereich von 256 x 256 Pixel angezeigt werden. ( Referenz )
  3. Für jede höhere Zoomstufe verdoppelt sich die Anzahl der Pixel, die zur Anzeige desselben Bereichs erforderlich sind, sowohl in der Breite als auch in der Höhe. ( Referenz )
  4. Karten werden in Längsrichtung, nicht jedoch in Breitenrichtung gewickelt.
249
John S

Für Version 3 der API ist dies einfach und funktioniert:

var latlngList = [];
latlngList.Push(new google.maps.LatLng(lat, lng));

var bounds = new google.maps.LatLngBounds();
latlngList.each(function(n) {
    bounds.extend(n);
});

map.setCenter(bounds.getCenter()); //or use custom center
map.fitBounds(bounds);

und einige optionale Tricks:

//remove one zoom level to ensure no marker is on the Edge.
map.setZoom(map.getZoom() - 1); 

// set a minimum zoom 
// if you got only 1 marker or all markers are on the same address map will be zoomed too much.
if(map.getZoom() > 15){
    map.setZoom(15);
}
34
d.raev

Da alle anderen Antworten Probleme mit der einen oder anderen Art von Umständen zu haben scheinen (Breite/Höhe der Karte, Breite/Höhe der Begrenzungen usw.), dachte ich, ich würde meine Antwort hier stellen ...

Es gab eine sehr nützliche Javascript-Datei hier: http://www.polyarc.us/adjust.js

Ich habe das als Basis dafür benutzt:

var com = com || {};
com.local = com.local || {};
com.local.gmaps3 = com.local.gmaps3 || {};

com.local.gmaps3.CoordinateUtils = new function() {

   var OFFSET = 268435456;
   var RADIUS = OFFSET / Math.PI;

   /**
    * Gets the minimum zoom level that entirely contains the Lat/Lon bounding rectangle given.
    *
    * @param {google.maps.LatLngBounds} boundary the Lat/Lon bounding rectangle to be contained
    * @param {number} mapWidth the width of the map in pixels
    * @param {number} mapHeight the height of the map in pixels
    * @return {number} the minimum zoom level that entirely contains the given Lat/Lon rectangle boundary
    */
   this.getMinimumZoomLevelContainingBounds = function ( boundary, mapWidth, mapHeight ) {
      var zoomIndependentSouthWestPoint = latLonToZoomLevelIndependentPoint( boundary.getSouthWest() );
      var zoomIndependentNorthEastPoint = latLonToZoomLevelIndependentPoint( boundary.getNorthEast() );
      var zoomIndependentNorthWestPoint = { x: zoomIndependentSouthWestPoint.x, y: zoomIndependentNorthEastPoint.y };
      var zoomIndependentSouthEastPoint = { x: zoomIndependentNorthEastPoint.x, y: zoomIndependentSouthWestPoint.y };
      var zoomLevelDependentSouthEast, zoomLevelDependentNorthWest, zoomLevelWidth, zoomLevelHeight;
      for( var zoom = 21; zoom >= 0; --zoom ) {
         zoomLevelDependentSouthEast = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentSouthEastPoint, zoom );
         zoomLevelDependentNorthWest = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentNorthWestPoint, zoom );
         zoomLevelWidth = zoomLevelDependentSouthEast.x - zoomLevelDependentNorthWest.x;
         zoomLevelHeight = zoomLevelDependentSouthEast.y - zoomLevelDependentNorthWest.y;
         if( zoomLevelWidth <= mapWidth && zoomLevelHeight <= mapHeight )
            return zoom;
      }
      return 0;
   };

   function latLonToZoomLevelIndependentPoint ( latLon ) {
      return { x: lonToX( latLon.lng() ), y: latToY( latLon.lat() ) };
   }

   function zoomLevelIndependentPointToMapCanvasPoint ( point, zoomLevel ) {
      return {
         x: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.x, zoomLevel ),
         y: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.y, zoomLevel )
      };
   }

   function zoomLevelIndependentCoordinateToMapCanvasCoordinate ( coordinate, zoomLevel ) {
      return coordinate >> ( 21 - zoomLevel );
   }

   function latToY ( lat ) {
      return OFFSET - RADIUS * Math.log( ( 1 + Math.sin( lat * Math.PI / 180 ) ) / ( 1 - Math.sin( lat * Math.PI / 180 ) ) ) / 2;
   }

   function lonToX ( lon ) {
      return OFFSET + RADIUS * lon * Math.PI / 180;
   }

};

Sie können dies auf jeden Fall bereinigen oder minimieren, wenn nötig, aber ich habe die Variablennamen lange aufbewahrt, um sie verständlicher zu machen.

Wenn Sie sich fragen, woher OFFSET stammt, ist 268435456 anscheinend die Hälfte des Erdumfangs in Pixel bei Zoomstufe 21 (gemäß http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with/) -Google Maps ).

1
Shadow Man

Danke, das hat mir sehr geholfen, den geeignetsten Zoomfaktor zu finden, um eine Polylinie richtig anzuzeigen. Ich finde die maximalen und minimalen Koordinaten unter den Punkten, die ich verfolgen muss, und falls der Pfad sehr "vertikal" ist, habe ich Ein paar Zeilen Code hinzugefügt:

var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = <?php echo $minLng; ?>;
var east = <?php echo $maxLng; ?>;
*var north = <?php echo $maxLat; ?>;*
*var south = <?php echo $minLat; ?>;*
var angle = east - west;
if (angle < 0) {
    angle += 360;
}
*var angle2 = north - south;*
*if (angle2 > angle) angle = angle2;*
var zoomfactor = Math.round(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2);

Tatsächlich ist der ideale Zoomfaktor Zoomfaktor 1.

1
Valerio Giacosa

map.getBounds() ist keine vorübergehende Operation, daher verwende ich in ähnlichen Fällen den Event-Handler. Hier ist mein Beispiel in Coffeescript

@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
  @map.setZoom(12) if @map.getZoom() > 12
0
MikDiet

Valerio hat mit seiner Lösung fast recht, aber es liegt ein logischer Fehler vor.

sie müssen zuerst prüfen, ob angle2 größer als Winkel ist, bevor Sie bei einem negativen Wert 360 addieren. 

ansonsten haben Sie immer einen größeren Wert als den Winkel

Die richtige Lösung ist also: 

var west = calculateMin(data.longitudes);
var east = calculateMax(data.longitudes);
var angle = east - west;
var north = calculateMax(data.latitudes);
var south = calculateMin(data.latitudes);
var angle2 = north - south;
var zoomfactor;
var delta = 0;
var horizontal = false;

if(angle2 > angle) {
    angle = angle2;
    delta = 3;
}

if (angle < 0) {
    angle += 360;
}

zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2) - 2 - delta;

Delta ist da, weil ich eine größere Breite als Höhe habe.

0
Lukas Olsen

Arbeitsbeispiel zum Ermitteln eines durchschnittlichen Standardzentrums mit React-Google-Maps in ES6

const bounds = new google.maps.LatLngBounds();
paths.map((latLng) => bounds.extend(new google.maps.LatLng(latLng)));
const defaultCenter = bounds.getCenter();
<GoogleMap
 defaultZoom={paths.length ? 12 : 4}
 defaultCenter={defaultCenter}
>
 <Marker position={{ lat, lng }} />
</GoogleMap>
0
Gapur Kassym