Google Maps V3-Cómo calcular el nivel de zoom para un determinado límite


Estoy buscando una manera de calcular el nivel de zoom para un determinado límite usando la API de Google Maps V3, similar a getBoundsZoomLevel() en la API de V2.

Esto es lo que quiero hacer:

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

Desafortunadamente, no puedo usar el método fitBounds() para mi caso de uso particular. Funciona bien para colocar marcadores en el mapa, pero no funciona bien para establecer límites exactos. Aquí hay un ejemplo de por qué no puedo usar el método fitBounds ().

map.fitBounds(map.getBounds()); // not what you expect
Author: Oliver, 2011-05-18

8 answers

Una pregunta similar se ha hecho en el grupo de Google: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

Los niveles de zoom son discretos, con la escala duplicándose en cada paso. Por lo tanto, en general no puede ajustarse a los límites que desea exactamente (a menos que tenga mucha suerte con el tamaño del mapa en particular).

Otro problema es la relación entre las longitudes de los lados, por ejemplo, no puede ajustar los límites exactamente a un rectángulo delgado dentro de un cuadrado asignar.

No hay una respuesta fácil para cómo ajustar los límites exactos, porque incluso si está dispuesto a cambiar el tamaño del div del mapa, tiene que elegir qué tamaño y nivel de zoom correspondiente cambia (en términos generales, ¿lo hace más grande o más pequeño de lo que es actualmente?).

Si realmente necesita calcular el zoom, en lugar de almacenarlo, esto debería hacer el truco:

La proyección de Mercator deforma la latitud, pero cualquier diferencia de longitud siempre representa lo mismo fracción del ancho del mapa (la diferencia de ángulo en grados / 360). En zoom cero, todo el mapa del mundo es de 256x256 píxeles, y el zoom de cada nivel duplica el ancho y la altura. Así que después de un poco de álgebra podemos calcular el zoom de la siguiente manera, siempre que sepamos el ancho del mapa en píxeles. Tenga en cuenta que debido a que la longitud se envuelve alrededor, tenemos que asegurarnos de que el ángulo es positivo.

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
Author: Giles Gardam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-20 22:22:25

Gracias a Giles Gardam por su respuesta, pero solo aborda la longitud y no la latitud. Una solución completa debe calcular el nivel de zoom necesario para la latitud y el nivel de zoom necesario para la longitud, y luego tomar el más pequeño (más lejos) de los dos.

Aquí hay una función que usa latitud y longitud:

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 en jsfiddle

Parámetros:

El valor del parámetro "límites" debe ser un google.maps.LatLngBounds objeto.

El valor del parámetro "mapDim" debe ser un objeto con las propiedades "height" y "width" que representan la altura y la anchura del elemento DOM que muestra el mapa. Es posible que desee disminuir estos valores si desea garantizar el relleno. Es decir, es posible que no desee que los marcadores de mapa dentro de los límites estén demasiado cerca del borde del mapa.

Si está utilizando la biblioteca jQuery, el valor mapDim se puede obtener de la siguiente manera:

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

Si está utilizando la biblioteca de prototipos, el valor de mapDim se puede obtener de la siguiente manera:

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

Valor de retorno:

El valor devuelto es el nivel de zoom máximo que aún mostrará los límites completos. Este valor estará entre 0 y el nivel máximo de zoom, inclusive.

El nivel máximo de zoom es 21. (Creo que fue solo 19 para Google Maps API v2.)


Explicación:

Google Maps utiliza una proyección de Mercator. En una proyección de Mercator las líneas de longitud están igualmente espaciadas, pero la las líneas de latitud no lo son. La distancia entre las líneas de latitud aumenta a medida que van desde el ecuador hasta los polos. De hecho, la distancia tiende hacia el infinito a medida que alcanza los polos. Un mapa de Google Maps, sin embargo, no muestra latitudes por encima de aproximadamente 85 grados Norte o por debajo de aproximadamente -85 grados Sur. (referencia ) (Calculo el corte real en +/-85.05112877980658 grados.)

Esto hace que el cálculo de las fracciones para los límites más complicado para latitud que longitud. Usé una fórmula de Wikipedia para calcular la fracción de latitud. Asumo que esto coincide con la proyección utilizada por Google Maps. Después de todo, la página de documentación de Google Maps a la que enlazo arriba contiene un enlace a la misma página de Wikipedia.

Otras notas:

  1. Los niveles de zoom van desde 0 hasta el nivel de zoom máximo. El nivel de zoom 0 es el mapa totalmente alejado. Los niveles más altos amplían el mapa aún más. (referencia )
  2. En zoom nivel 0 el mundo entero se puede mostrar en un área que es de 256 x 256 píxeles. (referencia )
  3. Para cada nivel de zoom más alto, el número de píxeles necesarios para mostrar la misma área se duplica tanto en ancho como en alto. (referencia )
  4. Los mapas se envuelven en la dirección longitudinal, pero no en la dirección latitudinal.
 229
Author: John S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-11 16:30:07

Para la versión 3 de la API, esto es simple y funcional:

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

Y algunos trucos opcionales:

//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
Author: d.raev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-23 06:29:00

Gracias, eso me ayudó mucho a encontrar el factor de zoom más adecuado para mostrar correctamente una polilínea. Encuentro las coordenadas máximas y mínimas entre los puntos que tengo que rastrear y, en caso de que el camino sea muy "vertical", solo agregué algunas líneas de código:

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 realidad, el factor de zoom ideal es zoomfactor-1.

 1
Author: Valerio Giacosa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-17 10:44:56

Ya que todas las otras respuestas parecen tener problemas para mí con una u otra serie de circunstancias (ancho/alto del mapa, ancho/alto de los límites, etc. Pensé en poner mi respuesta aquí...

Aquí había un archivo javascript muy útil: http://www.polyarc.us/adjust.js

Usé eso como base para esto:

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

};

Ciertamente puede limpiar esto o minimizarlo si es necesario, pero mantuve los nombres de las variables durante mucho tiempo en un intento de hacerlo más fácil de entender.

Si se pregunta de dónde vino el DESPLAZAMIENTO, aparentemente 268435456 es la mitad de la circunferencia de la tierra en píxeles en el nivel de zoom 21 (de acuerdo con http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps).

 1
Author: Shadow Man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-13 22:45:54

Valerio está casi en lo cierto con su solución, pero hay algún error lógico.

Primero debe verificar si angle2 es mayor que angle, antes de agregar 360 en negativo.

De lo contrario, siempre tendrá un valor mayor que angle

Así que la solución correcta es:

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á ahí, porque tengo un ancho más grande que alto.

 0
Author: Lukas Olsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-31 16:25:04

map.getBounds() no es una operación momentánea, por lo que uso en casos similares controlador de eventos. Aquí está mi ejemplo en Coffeescript

@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
  @map.setZoom(12) if @map.getZoom() > 12
 0
Author: MikDiet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-14 11:18:32

Ejemplo de trabajo para encontrar el centro predeterminado promedio con react-google-maps en 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
Author: Gapur Kassym,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-09 06:17:27