Google Maps V3 – Come calcolare il livello di zoom per un determinato limite

Sto cercando un modo per calcolare il livello di zoom per un determinato limite utilizzando l’API di Google Maps V3, simile a getBoundsZoomLevel () nell’API V2.

Ecco cosa voglio fare:

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

Sfortunatamente, non posso usare il metodo fitBounds () per il mio particolare caso d’uso. Funziona bene per montare i marcatori sulla mappa, ma non funziona bene per impostare limiti esatti. Ecco un esempio del perché non posso usare il metodo fitBounds ().

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

Una domanda simile è stata posta al gruppo Google: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

I livelli di zoom sono discreti, con la scala che raddoppia in ogni passaggio. Quindi in generale non puoi adattare esattamente i limiti che vuoi (a meno che tu non sia molto fortunato con la particolare dimensione della mappa).

Un altro problema è il rapporto tra lunghezze laterali, ad esempio non è ansible adattare i limiti esattamente a un rettangolo sottile all’interno di una mappa quadrata.

Non c’è una risposta facile su come adattare i limiti esatti, perché anche se si è disposti a cambiare la dimensione della mappa div, è necessario scegliere la dimensione e il livello di zoom corrispondente a cui si passa (in parole povere, lo si ingrandisce o si riduce di quello che è attualmente?).

Se hai davvero bisogno di calcolare lo zoom, piuttosto che memorizzarlo, questo dovrebbe fare il trucco:

La proiezione di Mercator deforma la latitudine, ma qualsiasi differenza di longitudine rappresenta sempre la stessa frazione della larghezza della mappa (la differenza di angolo in gradi / 360). Con lo zoom zero, l’intera mappa del mondo è 256×256 pixel e lo zoom su ogni livello raddoppia sia la larghezza che l’altezza. Quindi, dopo una piccola algebra, possiamo calcolare lo zoom come segue, a condizione che conosciamo la larghezza della mappa in pixel. Nota che poiché la longitudine si avvolge, dobbiamo assicurarci che l’angolo sia 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); 

Grazie a Giles Gardam per la sua risposta, ma si rivolge solo alla longitudine e non alla latitudine. Una soluzione completa dovrebbe calcolare il livello di zoom necessario per la latitudine e il livello di zoom necessario per la longitudine, e quindi prendere il più piccolo (più lontano) dei due.

Ecco una funzione che utilizza sia la latitudine sia la longitudine:

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

parametri:

Il valore del parametro "bounds" dovrebbe essere un object google.maps.LatLngBounds .

Il valore del parametro "mapDim" deve essere un object con proprietà "height" e "width" che rappresentano l'altezza e la larghezza dell'elemento DOM che visualizza la mappa. Potresti voler ridurre questi valori se vuoi assicurarti il ​​riempimento. Cioè, potresti non volere che gli indicatori di mappa all'interno dei limiti siano troppo vicini al bordo della mappa.

Se stai usando la libreria jQuery, il valore mapDim può essere ottenuto come segue:

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

Se si utilizza la libreria Prototype, il valore mapDim può essere ottenuto come segue:

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

Valore di ritorno:

Il valore restituito è il livello massimo di zoom che visualizzerà comunque i limiti completi. Questo valore sarà compreso tra 0 e il massimo livello di zoom, incluso.

Il massimo livello di zoom è 21. (Credo che fosse solo 19 per Google Maps API v2.)


Spiegazione:

Google Maps utilizza una proiezione di Mercator. In una proiezione di Mercatore le linee di longitudine sono equidistanti, ma le linee di latitudine non lo sono. La distanza tra le linee di latitudine aumenta man mano che vanno dall'equatore ai poli. In effetti la distanza tende all'infinito quando raggiunge i poli. Una mappa di Google Maps, tuttavia, non mostra latitudini superiori a circa 85 gradi nord o al di sotto di circa -85 gradi sud. ( riferimento ) (calcolo il taglio effettivo a +/- 85.05112877980658 gradi.)

Questo rende il calcolo delle frazioni per i limiti più complicato per latitudine che per longitudine. Ho usato una formula da Wikipedia per calcolare la frazione di latitudine. Presumo che questo corrisponda alla proiezione utilizzata da Google Maps. Dopotutto, la pagina di documentazione di Google Maps di cui sopra contiene un link alla stessa pagina di Wikipedia.

Altre note:

  1. I livelli di zoom vanno da 0 al massimo livello di zoom. Il livello di zoom 0 è la mappa completamente ingrandita. Livelli più alti ingrandiscono ulteriormente la mappa. ( riferimento )
  2. A livello di zoom 0 è ansible visualizzare l'intero mondo in un'area di 256 x 256 pixel. ( riferimento )
  3. Per ciascun livello di zoom superiore, il numero di pixel necessari per visualizzare la stessa area raddoppia in larghezza e in altezza. ( riferimento )
  4. Le mappe si avvolgono nella direzione longitudinale, ma non nella direzione latitudinale.

Per la versione 3 dell’API, questo è semplice e funzionante:

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

e alcuni trucchi facoltativi:

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

Grazie, mi ha aiutato molto a trovare il fattore di zoom più adatto per visualizzare correttamente una polilinea. Trovo le coordinate massime e minime tra i punti che devo tracciare e, nel caso in cui il percorso sia molto “verticale”, ho appena aggiunto poche righe di codice:

 var GLOBE_WIDTH = 256; // a constant in Google's map projection var west = ; var east = ; *var north = ;* *var south = ;* 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); 

In realtà, il fattore di zoom ideale è Zoomfactor-1.

Poiché tutte le altre risposte sembrano avere problemi per me con uno o l’altro insieme di circostanze (larghezza / altezza della mappa, larghezza / altezza dei limiti, ecc.) Ho pensato di mettere la mia risposta qui …

C’era un file javascript molto utile qui: http://www.polyarc.us/adjust.js

L’ho usato come base per questo:

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

Puoi certamente ripulirlo o ridurlo se necessario, ma ho mantenuto a lungo i nomi delle variabili nel tentativo di renderlo più facile da capire.

Se ti stai chiedendo da dove viene OFFSET, apparentemente 268435456 è la metà della circonferenza terrestre in pixel con il livello di zoom 21 (secondo http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google -maps ).

Valerio ha quasi ragione con la sua soluzione, ma c’è qualche errore logico.

devi prima controllare se l’angolo2 è più grande dell’angolo, prima di aggiungere 360 ​​in negativo.

altrimenti hai sempre un valore maggiore dell’angolo

Quindi la soluzione corretta è:

 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 è lì, perché ho una larghezza maggiore dell'altezza.

map.getBounds() non è un’operazione momentanea, quindi uso in un gestore di eventi del caso simile. Ecco il mio esempio in Coffeescript

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

Esempio di lavoro per trovare il centro medio predefinito con react-google-maps su ES6 :

 const bounds = new google.maps.LatLngBounds(); paths.map((latLng) => bounds.extend(new google.maps.LatLng(latLng))); const defaultCenter = bounds.getCenter();