Google Maps V3 – 如何计算给定界限的缩放级别

我正在寻找一种方法来使用Google Maps V3 API计算给定边界的缩放级别,类似于V2 API中的getBoundsZoomLevel()。

这是我想要做的:

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

不幸的是,我不能为我的特殊用例使用fitBounds()方法。 它可以很好地适应地图上的标记,但是对于设置确切的边界并不适用。 这是一个为什么我不能使用fitBounds()方法的例子。

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

Google小组提出了一个类似的问题: http : //groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

缩放级别是离散的,每个步骤的缩放倍数。 所以一般来说,你不能完全符合你想要的边界(除非你对特定的地图大小感到非常幸运)。

另一个问题是边长之间的比例,例如,你不能准确地将边界拟合到方形地图内的一个薄矩形。

对于如何适合确切的边界,并不是一个简单的答案,因为即使你愿意改变地图div的大小,你也必须select你改变的大小和相应的缩放级别(粗略地说,你是把它放大还是缩小比现在要多?)。

如果你真的需要计算缩放,而不是存储它,这应该做的伎俩:

墨卡托投影翘曲纬度,但经度的任何差异总是表示地图的宽度的相同部分(以度/ 360度的angular度差异)。 在缩放零点时,整个世界地图是256×256像素,缩放每个级别宽度和高度加倍。 所以在一个小代数之后,我们可以按如下方式计算缩放比例,只要我们知道地图的像素宽度即可。 请注意,由于经度环绕,我们必须确保angular度是正的。

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

感谢Giles Gardam的回答,但它只涉及经度而不是纬度。 一个完整的解决scheme应该计算纬度所需的缩放级别和经度所需的缩放级别,然后取两个中较小(更远的)。

这是一个同时使用经度和纬度的函数:

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

在jsfiddle上演示

参数:

“bounds”参数值应该是一个google.maps.LatLngBounds对象。

“mapDim”参数值应该是一个具有“height”和“width”属性的对象,这些属性表示显示地图的DOM元素的高度和宽度。 如果你想确保填充,你可能想减less这些值。 也就是说,您可能不希望边界内的地图标记太靠近地图的边缘。

如果您正在使用jQuery库,则可以按如下所示获取mapDim值:

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

如果您正在使用Prototype库,则可以按如下所示获取mapDim值:

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

返回值:

返回值是仍将显示整个边界的最大缩放级别。 此值将介于0和最大缩放级别之间,包括在内。

最大缩放级别是21.(我相信Google Maps API v2只有19级。)


说明:

Google地图使用墨卡托投影。 在墨卡托投影中,经线是等距的,但是纬线不是。 纬线之间的距离随着从赤道到极点而增加。 事实上,当它到达极点时,距离趋于无穷大。 然而,Google Maps地图不会显示北纬85度以上或南纬约85度以下的纬度。 ( 参考 )(我计算+/- 85.05112877980658度的实际截止点。)

这使得纬度分数的计算比经度更复杂。 我用维基百科的公式来计算纬度分数。 我假设这与Google地图使用的投影相符。 毕竟,我链接到上面的Google Maps文档页面包含指向同一个Wikipedia页面的链接。

其他说明:

  1. 缩放级别范围从0到最大缩放级别。 缩放级别0是完全缩小的地图。 更高级别进一步放大地图。 ( 参考 )
  2. 在缩放级别0时,整个世界可以显示在256×256像素的区域中。 ( 参考 )
  3. 对于每个较高的缩放级别,显示同一区域所需的像素数量在宽度和高度上都是双倍的。 ( 参考 )
  4. 地图在纵向上包裹,但不在纬度方向上包裹。

对于API的第3版,这很简单,而且是可行的:

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

和一些可选的技巧:

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

谢谢,这帮助我find了最合适的缩放因子来正确显示多段线。 我发现我必须跟踪的点之间的最大和最小坐标,如果path非常“垂直”,我只是添加了几行代码:

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

实际上,理想的缩放因子是zoomfactor-1。

由于所有其他的答案似乎有一个或另一个环境(地图的宽度/高度,边界宽度/高度等)的问题,我想我会把我的答案在这里…

这里有一个非常有用的JavaScript文件: http : //www.polyarc.us/adjust.js

我用这个作为基础:

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

如果需要的话,你可以清理掉它,或者把它缩小,但是为了更容易理解,我保留了variables名。

如果您想知道OFFSET从哪里来,显然268435456是缩放级别为21的地球像素周长的一半(根据http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google -maps )。

瓦莱里奥对他的解决scheme几乎是正确的,但有一些逻辑错误。

你必须首先检查它的angular度2是否大于angular度,然后再加360。

否则你总会有比angular度更大的价值

所以正确的解决scheme是:

 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; 

三angular洲在那里,因为我有一个比高度更大的宽度。

map.getBounds()不是瞬间操作,所以我在类似的情况下使用事件处理程序。 这是我在Coffeescript中的例子

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