nick clark
Estoy buscando una manera de calcular el nivel de zoom para un límite dado usando la API de Google Maps V3, similar a getBoundsZoomLevel()
en la API 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 fitBounds()
método 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 fitBounds()
método.
map.fitBounds(map.getBounds()); // not what you expect
Juan S.
Gracias a Giles Gardam por su respuesta, pero solo aborda la longitud y no la latitud. Una solución completa debería 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 alejado) de los dos.
Aquí hay una función que usa tanto la latitud como la 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);
}
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 propiedades de “alto” y “ancho” que representen el alto y el ancho 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 mapDim
valor se puede obtener de la siguiente manera:
var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };
Si está utilizando la biblioteca Prototype, el valor 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 seguirá mostrando los límites completos. Este valor estará entre 0
y el nivel máximo de zoom, inclusive.
El nivel de zoom máximo es 21. (Creo que solo fue 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 las líneas de latitud no lo están. La distancia entre líneas de latitud aumenta a medida que van del ecuador a los polos. De hecho la distancia tiende hacia el infinito al llegar a los polos. Sin embargo, un mapa de Google Maps no muestra latitudes por encima de aproximadamente 85 grados norte o por debajo de aproximadamente -85 grados sur. (referencia) (Calculo el límite real en +/-85.05112877980658 grados).
Esto hace que el cálculo de las fracciones de los límites sea más complicado para la latitud que para la longitud. usé un fórmula de Wikipedia para calcular la fracción de latitud. Supongo 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 vinculo anteriormente contiene un enlace a la misma página de Wikipedia.
Otras notas:
- Los niveles de zoom van desde 0 hasta el nivel de zoom máximo. El nivel de zoom 0 es el mapa completamente alejado. Los niveles más altos acercan más el mapa. (referencia)
- En el nivel de zoom 0, el mundo entero se puede mostrar en un área de 256 x 256 píxeles. (referencia)
- 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)
- Los mapas se ajustan en la dirección longitudinal, pero no en la dirección latitudinal.
-
@John S: esta es una solución fantástica y estoy contemplando usarla sobre el método fitBounds nativo de Google Maps disponible para mí también. Me di cuenta de que fitBounds a veces está un nivel de zoom hacia atrás (alejamiento), pero supongo que eso se debe al relleno que está agregando. Entonces, ¿la única diferencia entre este y el método fitBounds es la cantidad de relleno que desea agregar que explica el cambio en el nivel de zoom entre los dos?
– johntrepreneur
25/06/2013 a las 19:40
-
@johntrepreneur – Ventaja n.º 1: puede usar este método incluso antes de crear el mapa, por lo que puede proporcionar su resultado a la configuración inicial del mapa. Con
fitBounds
debe crear el mapa y luego esperar el evento “bounds_changed”.– Juan S.
25/06/2013 a las 21:40
-
@MarianPaździoch – Funciona para esos límites, mira aquí. ¿Espera poder hacer zoom para que esos puntos estén en las esquinas exactas del mapa? Eso no es posible porque los niveles de zoom son valores enteros. La función devuelve el nivel de zoom más alto que aún incluirá los límites completos en el mapa.
– Juan S.
28 de enero de 2015 a las 0:16
-
@CarlMeyer: no lo menciono en mi respuesta, pero en un comentario anterior afirmo que una ventaja de esta función es “Puede usar este método incluso antes de crear el mapa”. Usando
map.getProjection()
eliminaría algunas de las matemáticas (y la suposición sobre la proyección), pero significaría que la función no podría llamarse hasta después de que se haya creado el mapa y se haya activado el evento “projection_changed”.– Juan S.
21 de abril de 2016 a las 18:01
-
@hugoderhungrige: vea la nota n. ° 2 en la respuesta. Cuando un mapa de Google se amplía completamente (hasta el nivel 0), todo el mundo se muestra en una imagen con dimensiones de 256 x 256 píxeles.
– Juan S.
27 abr 2021 a las 22:46
Giles Gardam
Se ha hecho una pregunta similar 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. Entonces, en general, no puede ajustarse exactamente a los límites que desea (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 mapa cuadrado.
No hay una respuesta fácil sobre cómo ajustar los límites exactos, porque incluso si está dispuesto a cambiar el tamaño de la división del mapa, debe elegir el tamaño y el nivel de zoom correspondiente al que 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 funcionar:
La proyección de Mercator deforma la latitud, pero cualquier diferencia de longitud siempre representa la misma fracción del ancho del mapa (la diferencia de ángulo en grados/360). Con el zoom cero, el mapa mundial completo tiene 256×256 píxeles, y al hacer zoom en cada nivel se duplica el ancho y la altura. Entonces, 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, debemos asegurarnos de que el ángulo sea 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);
-
¿No tendrías que repetir esto para el lat y luego elegir el min del resultado de los 2? No creo que esto funcione para límites altos y estrechos…
– átomo blanco
25 de marzo de 2012 a las 3:15
-
Funciona muy bien para mí con un cambio de Math.round a Math.floor. Un millón de gracias.
– Pete
27 de abril de 2012 a las 13:54
-
¿Cómo puede ser esto correcto si no tiene en cuenta la latitud? Cerca del ecuador debería estar bien, pero la escala del mapa en un nivel de zoom determinado cambia según la latitud.
– Eyal
17 de mayo de 2012 a las 8:19
-
@Pete, buen punto, en general, probablemente querrá redondear hacia abajo el nivel de zoom para que quepa un poco más de lo deseado en el mapa, en lugar de un poco menos. Usé Math.round porque en la situación del OP, el valor antes del redondeo debería ser aproximadamente integral.
– Giles Gardam
15 de julio de 2012 a las 8:16
-
¿Cuál es el valor de pixelWidth?
– Alberto Jegani
23 de febrero de 2015 a las 14:27
d.raev
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);
}
-
¿Por qué no establecer un nivel de zoom mínimo al inicializar el mapa, algo como: var mapOptions = { maxZoom: 15, };
– kush
12 de agosto de 2014 a las 7:22
-
@Kush, buen punto. pero
maxZoom
evitará que el usuario manual hacer zoom Mi ejemplo solo cambia el DefaultZoom y solo si es necesario.– d.raev
12 de agosto de 2014 a las 9:03
-
cuando haces fitBounds, simplemente salta para ajustarse a los límites en lugar de animarse allí desde la vista actual. la solución impresionante es mediante el uso ya mencionado
getBoundsZoomLevel
. de esa manera, cuando llamas a setZoom, se anima al nivel de zoom deseado. a partir de ahí, no es un problema hacer el panTo y terminas con una hermosa animación de mapa que se ajusta a los límites.– usuario151496
26 de febrero de 2015 a las 17:24
-
animación no se discute en la pregunta ni en mi respuesta. Si tiene un ejemplo útil sobre el tema, simplemente cree una respuesta constructiva, con un ejemplo y cómo y cuándo se puede usar.
– d.raev
26 de febrero de 2015 a las 17:46
-
Por alguna razón, el mapa de Google no hace zoom cuando se llama a setZoom() inmediatamente después de la llamada a map.fitBounds(). (gmaps es v3.25 actualmente)
– kashiraja
7 oct 2016 a las 23:11
Versión de dardo:
double latRad(double lat) {
final double sin = math.sin(lat * math.pi / 180);
final double radX2 = math.log((1 + sin) / (1 - sin)) / 2;
return math.max(math.min(radX2, math.pi), -math.pi) / 2;
}
double getMapBoundZoom(LatLngBounds bounds, double mapWidth, double mapHeight) {
final LatLng northEast = bounds.northEast;
final LatLng southWest = bounds.southWest;
final double latFraction = (latRad(northEast.latitude) - latRad(southWest.latitude)) / math.pi;
final double lngDiff = northEast.longitude - southWest.longitude;
final double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
final double latZoom = (math.log(mapHeight / 256 / latFraction) / math.ln2).floorToDouble();
final double lngZoom = (math.log(mapWidth / 256 / lngFraction) / math.ln2).floorToDouble();
return math.min(latZoom, lngZoom);
}
fotograve
Aquí una versión de Kotlin de la función:
fun getBoundsZoomLevel(bounds: LatLngBounds, mapDim: Size): Double {
val WORLD_DIM = Size(256, 256)
val ZOOM_MAX = 21.toDouble();
fun latRad(lat: Double): Double {
val sin = Math.sin(lat * Math.PI / 180);
val radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return max(min(radX2, Math.PI), -Math.PI) /2
}
fun zoom(mapPx: Int, worldPx: Int, fraction: Double): Double {
return floor(Math.log(mapPx / worldPx / fraction) / Math.log(2.0))
}
val ne = bounds.northeast;
val sw = bounds.southwest;
val latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / Math.PI;
val lngDiff = ne.longitude - sw.longitude;
val lngFraction = if (lngDiff < 0) { (lngDiff + 360) / 360 } else { (lngDiff / 360) }
val latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
val lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
return minOf(latZoom, lngZoom, ZOOM_MAX)
}
xcodesucks123
Ninguna de las respuestas altamente votadas funcionó para mí. Lanzaron varios errores indefinidos y terminaron calculando inf/nan para ángulos. Sospecho que quizás el comportamiento de LatLngBounds ha cambiado con el tiempo. En cualquier caso, encontré que este código funciona para mis necesidades, tal vez pueda ayudar a alguien:
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 getZoom(lat_a, lng_a, lat_b, lng_b) {
let latDif = Math.abs(latRad(lat_a) - latRad(lat_b))
let lngDif = Math.abs(lng_a - lng_b)
let latFrac = latDif / Math.PI
let lngFrac = lngDif / 360
let lngZoom = Math.log(1/latFrac) / Math.log(2)
let latZoom = Math.log(1/lngFrac) / Math.log(2)
return Math.min(lngZoom, latZoom)
}
Valerio Giacosa
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 la ruta 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.
-
me gustó
var zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2)-1;
. Aún así, muy útil.– Mojowen
29/06/2012 a las 20:30
Lo siento, enlace a una pregunta incorrecta, este es el enlace correcto.
– Tomás
24 de marzo de 2012 a las 8:09
Esta pregunta no es un duplicado de la otra pregunta. La respuesta a la otra pregunta es usar
fitBounds()
. Esta pregunta indaga qué hacer cuandofitBounds()
es insuficiente, ya sea porque hace demasiado zoom o porque no quiere hacer zoom (es decir, solo quiere el nivel de zoom).– Juan S.
9 mayo 2014 a las 15:31
@Nick Clark: ¿Cómo sabe que se deben establecer los límites sw, ne? ¿Cómo los capturaste antes?
– varaprakash
30 de mayo de 2014 a las 22:24