Google Maps V3 - comment calculer le niveau de zoom pour une borne donnée

je cherche un moyen de calculer le niveau de zoom pour une limite donnée en utilisant L'API V3 de Google Maps, similaire à getBoundsZoomLevel() dans L'API V2.

Voici ce que je veux faire:

// 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

malheureusement, je ne peux pas utiliser la méthode fitBounds() pour mon cas d'utilisation particulier. Il fonctionne bien pour l'installation de marqueurs sur la carte, mais il ne fonctionne pas bien pour l'établissement des limites exactes. Voici un exemple de pourquoi je ne peux pas utiliser la méthode fitBounds ().

map.fitBounds(map.getBounds()); // not what you expect
121
demandé sur Oliver 2011-05-18 21:57:35

8 réponses

une question similaire a été posée sur le groupe Google: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

les niveaux de zoom sont discrets, l'échelle doublant à chaque pas. Donc, en général, vous ne pouvez pas ajuster les limites que vous voulez exactement (sauf si vous êtes très chanceux avec la taille particulière de la carte).

un autre problème est le rapport entre les longueurs latérales par exemple, vous ne pouvez pas les limites exactement à un rectangle mince à l'intérieur d'une carte carrée.

il n'y a pas de réponse facile pour savoir comment ajuster les limites exactes, parce que même si vous êtes prêt à changer la taille de la div de carte, vous devez choisir la taille et le niveau de zoom correspondant que vous changez pour (en gros, est-ce que vous le faites plus grand ou plus petit que ce qu'il est actuellement?).

si vous avez vraiment besoin de calculer le zoom, plutôt que de le stocker, cela devrait faire l'affaire:

le La projection Mercator indique la latitude, mais toute différence de longitude représente toujours la même fraction de la largeur de la carte (différence d'angle en degrés / 360). À zoom zéro, la carte du monde entier est de 256x256 pixels, et zoomer sur chaque niveau double à la fois la largeur et la hauteur. Donc, après un peu d'algèbre, nous pouvons calculer le zoom comme suit, à condition de connaître la largeur de la carte en pixels. Notez que puisque la longitude s'enroule, nous devons nous assurer que l'angle est positif.

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);
94
répondu Giles Gardam 2011-10-20 22:22:25

merci à Giles Gardam pour sa réponse, mais elle ne concerne que la longitude et non la latitude. Une solution complète devrait calculer le niveau de zoom nécessaire pour la latitude et le niveau de zoom nécessaire pour la longitude, puis prendre le plus petit (plus loin) des deux.

Voici une fonction qui utilise à la fois la latitude et la longitude:

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);
}

Démo sur jsfiddle

paramètres:

la valeur du paramètre "bounds" doit être un objet google.maps.LatLngBounds .

la valeur du paramètre "mapDim" doit être un objet dont les propriétés "hauteur" et "largeur" représentent la hauteur et la largeur de l'élément DOM qui affiche la carte. Vous pouvez vouloir diminuer ces valeurs si vous voulez assurer le rembourrage. C'est, peut ne pas vouloir marqueurs de carte dans la limite d'être trop près du bord de la carte.

si vous utilisez la bibliothèque jQuery, la valeur mapDim peut être obtenue comme suit:

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

si vous utilisez la bibliothèque de prototypes, la valeur mapDim peut être obtenue comme suit:

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

Valeur De Retour:

la valeur de retour est le niveau de zoom maximum qui affichera encore les bornes entières. Cette valeur se situera entre 0 et le niveau de zoom maximal, inclusivement.

le niveau de zoom maximal est de 21. (Je crois qu'il n'y en avait que 19 pour L'API GoogleMaps v2.)


explication:

Google Maps utilise une projection Mercator. Dans une projection Mercator, les lignes de longitude sont également espacées, mais pas les lignes de latitude. La distance entre les lignes de latitude augmente au fur et à mesure qu'elles passent de l'Équateur aux pôles. En fait, la distance tend vers l'infini, il atteint des pôles. Google Cartes la carte, cependant, ne montre pas les latitudes au-dessus d'environ 85 degrés Nord ou au-dessous d'environ -85 degrés Sud. ( de référence ) (j'ai calculer la réelle coupure à +/-85.05112877980658 degrés.)

Cela rend le calcul des fractions pour les limites plus compliqué pour la latitude de longitude. J'ai utilisé une formule de Wikipedia pour calculer la fraction de latitude. Je suppose que cela correspond à la projection utilisée par Google Maps. Après tout, la page de documentation de Google Maps à laquelle je renvoie ci-dessus contient un lien vers la même page Wikipedia.

Autres Notes:

  1. les niveaux de Zoom vont de 0 au niveau de zoom maximal. Zoom niveau 0 est la carte entièrement agrandie. Les niveaux supérieurs zooment la carte plus loin. ( référence )
  2. au niveau de zoom 0 le monde entier peut être affiché dans une zone qui est de 256 x 256 pixels. ( référence )
  3. pour chaque niveau de zoom supérieur, le nombre de pixels nécessaires pour afficher la même zone double à la fois en largeur et en hauteur. ( référence )
  4. Cartes enveloppez-la dans le sens longitudinal, mais pas dans la direction de largeur.
229
répondu John S 2013-05-11 16:30:07

pour la version 3 de L'API, c'est simple et fonctionnel:

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);

et quelques astuces optionnelles:

//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);
}
31
répondu d.raev 2014-06-23 06:29:00

Merci, cela m'a beaucoup aidé à trouver le facteur de zoom le plus approprié pour afficher correctement un polyligne. Je trouve les coordonnées maximum et minimum parmi les points que je dois suivre et, si le chemin est très "vertical", je viens d'ajouter quelques lignes de code:

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);

en fait, le facteur de zoom idéal est zoomfactor-1.

1
répondu Valerio Giacosa 2012-05-17 10:44:56

puisque toutes les autres réponses semblent avoir des problèmes pour moi avec l'un ou l'autre ensemble de circonstances (largeur de la carte/hauteur, largeur des bornes/hauteur, etc.) J'ai pensé que je l'avais mis ma réponse ici...

il y avait un fichier javascript très utile ici: http://www.polyarc.us/adjust.js

Je l'ai utilisé comme base pour ceci:

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;
   }

};

Vous pouvez certainement nettoyer cette ou rapetisser si nécessaire, mais j'ai gardé l' noms de variables longues dans une tentative de le rendre plus facile à comprendre.

si vous vous demandez d'où vient L'OFFSET, apparemment 268435456 est la moitié de la circonférence de la terre en pixels au niveau de zoom 21 (selon http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps ).

1
répondu Shadow Man 2013-03-13 22:45:54

Valerio a presque raison avec sa solution, mais il y a une erreur logique.

vous devez d'abord vérifier que wether angle2 est plus grand que angle, avant d'ajouter 360 à un négatif.

sinon vous avez toujours une plus grande valeur que l'angle

ainsi la solution correcte est:

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 est là, parce que j'ai une plus grande largeur que la hauteur.

0
répondu Lukas Olsen 2012-08-31 16:25:04

map.getBounds() n'est pas une opération momentanée, donc j'utilise dans le même cas le gestionnaire d'événements. Voici mon exemple dans Coffeescript

@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
  @map.setZoom(12) if @map.getZoom() > 12
0
répondu MikDiet 2012-10-14 11:18:32

exemple de travail pour trouver le centre par défaut moyen avec react-google-maps sur 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
répondu Gapur Kassym 2018-04-09 06:17:27