web-dev-qa-db-fra.com

Google Maps V3 - Comment calculer le niveau de zoom pour une limite donnée

Je cherche un moyen de calculer le niveau de zoom d'une limite donnée à l'aide de l'API Google Maps V3, 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. Cela fonctionne bien pour ajuster les marqueurs sur la carte, mais pas pour définir des limites exactes. Voici un exemple de la raison pour laquelle je ne peux pas utiliser la méthode fitBounds().

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

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 étape. Donc, en général, vous ne pouvez pas respecter exactement les limites (à moins que vous ne soyez très chanceux avec la taille de la carte).

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

Il n’est pas facile de déterminer comment ajuster les limites exactes, car même si vous êtes prêt à modifier la taille de la carte, vous devez choisir la taille et le niveau de zoom correspondants (en gros, augmentez-vous la taille ou réduisez-vous qu’il est actuellement?).

Si vous avez vraiment besoin de calculer le zoom, plutôt que de le stocker, voici le résultat:

La projection de Mercator déforme 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). Au zoom zéro, la carte du monde entier mesure 256 x 256 pixels et chaque niveau de zoom double la largeur et la hauteur. Ainsi, 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 comme la longitude tourne autour, 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);
98
Giles Gardam

Merci à Giles Gardam pour sa réponse, mais cela ne concerne que la longitude et non la latitude. Une solution complète devrait calculer le niveau de zoom requis pour la latitude et le niveau de zoom requis 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);
}

Demo on 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 avec les propriétés "height" et "width" qui représentent la hauteur et la largeur de l'élément DOM qui affiche la carte. Vous voudrez peut-être diminuer ces valeurs si vous voulez assurer un remplissage. Autrement dit, vous ne voudrez peut-être pas que les marqueurs de carte situés dans les limites soient trop proches 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 affiche toujours les limites entières. Cette valeur sera comprise entre 0 et le niveau de zoom maximum, inclus.

Le niveau de zoom maximal est de 21. (Je crois que ce n'était que de 19 pour l'API Google Maps v2.)


Explication:

Google Maps utilise une projection de Mercator. Dans une projection de Mercator, les lignes de longitude sont à égale distance, mais les lignes de latitude ne le sont pas. La distance entre les lignes de latitude augmente lorsqu'elles vont de l'équateur aux pôles. En fait, la distance tend vers l’infini à mesure qu’elle atteint les pôles. Toutefois, une carte Google Maps ne montre pas les latitudes supérieures à environ 85 degrés nord ou inférieures à environ -85 degrés sud. ( référence ) (Je calcule la coupure réelle à +/- 85,05112877980658 degrés.)

Cela rend le calcul des fractions pour les limites plus compliqué pour la latitude que pour la longitude. J'ai utilisé un 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 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 maximum. Le niveau de zoom 0 correspond à un zoom arrière de la carte. Les niveaux supérieurs agrandissent davantage la carte. ( référence )
  2. Au niveau de zoom 0, le monde entier peut être affiché dans une zone de 256 x 256 pixels. ( référence )
  3. Pour chaque niveau de zoom supérieur, le nombre de pixels requis pour afficher la même zone double en largeur et en hauteur. ( référence )
  4. Les cartes s'enroulent dans le sens longitudinal, mais pas dans le sens latitudinal.
249
John S

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);
}
34
d.raev

Toutes les autres réponses semblant poser problème pour moi dans l'une ou l'autre situation (largeur/hauteur de la carte, limites largeur/hauteur, etc.), je pensais mettre 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 cela ou le réduire si nécessaire, mais j'ai gardé les noms de variables longtemps pour tenter de le rendre plus facile à comprendre.

Si vous vous demandez d’où vient OFFSET, apparemment, 268435456 correspond à la moitié du périmètre terrestre en pixels au niveau de zoom 21 (selon http://www.appelsiini.net/2008/introduction-to-marker-clustering-with -Google Maps ).

1
Shadow Man

Merci, cela m'a beaucoup aidé à trouver le facteur de zoom le plus approprié pour afficher correctement une polyligne ..__ Je trouve les coordonnées maximales et minimales entre les points à suivre et, si le chemin est très "vertical", je vient 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
Valerio Giacosa

map.getBounds() n'est pas une opération momentanée, je l'utilise donc dans un gestionnaire d'événements similaire. Voici mon exemple dans Coffeescript

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

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

vous devez d’abord vérifier si l’angle2 est supérieur à l’angle, avant d’ajouter 360 au négatif. 

sinon vous avez toujours une valeur supérieure à l'angle

La solution correcte est donc: 

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

0
Lukas Olsen

Exemple de travail pour trouver le centre moyen par défaut 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
Gapur Kassym