Google Maps V3-如何计算给定范围的缩放级别


138

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

这是我想做的:

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

4
最后一个例子非常好,非常说明问题!+1。我有同样的问题
TMS

抱歉,链接了错误的问题,这是正确的链接
TMS 2012年

这个问题不是另一个问题的重复。另一个问题的答案是使用fitBounds()。这个问题会问当fitBounds()不足时该怎么做-要么是因为缩放过度,要么是您不想缩放(即,您只想要缩放级别)。
约翰S

@尼克·克拉克:你怎么知道要设定的瑞士法郎?您之前是如何捕获它们的?
varaprakash 2014年

Answers:


108

在Google 网上论坛上也提出了类似的问题:http : //groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

缩放级别是离散的,缩放比例在每个步骤中加倍。因此,总的来说,您无法完全满足您的要求(除非您对特定的地图尺寸感到非常幸运)。

另一个问题是边长之间的比率,例如,您无法将边界完全适合正方形地图内的细矩形。

对于如何拟合精确范围没有简单的答案,因为即使您愿意更改map div的大小,也必须选择要更改为哪个大小和相应的缩放级别(大致来说,是将其放大还是缩小)比目前要高?)。

如果您确实需要计算缩放比例,而不是存储缩放比例,则可以这样做:

墨卡托投影会扭曲纬度,但经度上的任何差异始终代表地图宽度的相同部分(角度差异,以度/ 360为单位)。缩放为零时,整个世界地图为256x256像素,缩放每个级别会使宽度和高度都加倍。因此,经过一些代数运算后,只要我们知道地图的宽度(以像素为单位),就可以按以下方式计算缩放比例。请注意,由于经度回绕,因此必须确保角度为正。

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

1
您是否不必为纬度重复此操作,然后选择2的结果中的最小值?我认为这不太适合狭窄的范围.....
whiteatom

3
将Math.round更改为Math.floor对我来说很棒。太感谢了。
皮特

3
如果不考虑纬度怎么办?在赤道附近应该没问题,但是在给定缩放级别下,地图的比例会根据纬度而变化!
2012年

1
@Pete好点,通常,您可能需要舍入缩放级别,以使您在地图中的容纳比期望的要多,而不是减少。我使用Math.round是因为在OP的情况下,四舍五入之前的值应近似为整数。
吉尔斯·加丹

20
pixelWidth的值是多少
albert Jegani 2015年

279

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

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

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

参数:

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

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

如果使用的是jQuery库,则mapDim可以按以下方式获取值:

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

如果使用原型库,则可以按以下方式获取mapDim值:

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

返回值:

返回值是最大缩放级别,仍将显示整个范围。此值介于0和之间,包括最大缩放级别。

最大缩放级别为21。(我相信Google Maps API v2仅为19。)


说明:

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

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

其他说明:

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

6
很好的答案,这应该是经度和纬度最高的投票者。到目前为止工作完美。
彼得·伍斯特

1
@John S-这是一个很棒的解决方案,我正在考虑将其用于本人可用的本机google maps fitBounds方法。我注意到fitBounds有时是一个缩放级别(缩小),但是我认为这是从它添加的填充中得出的。那么,this和fitBounds方法之间的唯一区别只是您要添加的填充量,这两种填充量会导致两者之间的缩放级别发生变化吗?
johntrepreneur 2013年

1
@johntrepreneur-优势1:您甚至可以在创建地图之前使用此方法,因此可以将其结果提供给初始地图设置。使用fitBounds,您需要创建地图,然后等待“ bounds_changed”事件。
约翰·S

1
@MarianPaździoch-它确实适用于该范围,请参见此处。您是否希望能够缩放,以便这些点位于地图的确切角上?这是不可能的,因为缩放级别是整数值。该函数返回最高缩放级别,该级别仍将包括地图上的整个范围。
约翰·S

1
@CarlMeyer-我没有在答案中提及它,但在上面的评论中,我指出此功能的一个优点是“您甚至可以在创建地图之前使用此方法”。使用map.getProjection()会消除一些数学运算(以及有关投影的假设),但是这意味着只有在创建地图并触发“ projection_changed”事件后才能调用该函数。
John S

39

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

1
为什么在初始化地图时不设置最小缩放级别,例如:var mapOptions = {maxZoom:15,};
库什(Kush)2014年

2
@库什,很好。但maxZoom会阻止用户手动缩放。我的示例仅在必要时更改DefaultZoom。
d.raev 2014年

1
当您执行fitBounds时,它会跳转以适合边界,而不是从当前视图中对其进行动画处理。很棒的解决方案是使用已经提到的方法getBoundsZoomLevel。这样,当您调用setZoom时,它将动画化为所需的缩放级别。从那里开始panTo没问题,最终您会得到一个适合边界的精美地图动画
user151496 2015年

在问题或答案中都没有讨论动画。如果您有关于该主题的有用示例,则只需创建一个建设性的答案,并提供示例以及示例以及如何使用它以及何时使用它。
d.raev

由于某些原因,在map.fitBounds()调用后立即调用setZoom()时,Google地图无法缩放。(gmaps当前为v3.25)
kashiraja

4

这是该功能的Kotlin版本:

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

1

谢谢,这对我找到最合适的缩放系数以正确显示折线很有帮助。我找到了必须跟踪的点之间的最大和最小坐标,并且如果路径非常“垂直”,我只添加了几行代码:

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。


我喜欢var zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2)-1;。仍然非常有帮助。
Mojowen 2012年

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

};

您当然可以清理掉它,或者在需要时缩小它,但是我将变量名保留了很长一段时间,以使其易于理解。

如果您想知道偏移量的来源,那么268435456显然是缩放级别21的地球周长(以像素为单位)(根据http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google -maps)。


0

Valerio的解决方案几乎是正确的,但存在一些逻辑错误。

您必须先检查angle2是否大于angle,然后再将360设为负数。

否则,您的价值总是大于角度

因此正确的解决方案是:

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;

三角洲在那里,因为我的宽度比高度大。


0

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

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

0

一个工作示例,以在上找到带有react-google-maps的平均默认中心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

Giles Gardam经度的缩放级别的计算对我来说很好。如果要计算纬度的缩放系数,这是一个很好的简单解决方案:

double minLat = ...;
double maxLat = ...;
double midAngle = (maxLat+minLat)/2;
//alpha is the non-negative angle distance of alpha and beta to midangle
double alpha  = maxLat-midAngle;
//Projection screen is orthogonal to vector with angle midAngle
//portion of horizontal scale:
double yPortion = Math.sin(alpha*Math.pi/180) / 2;
double latZoom = Math.log(mapSize.height / GLOBE_WIDTH / yPortion) / Math.ln2;

//return min (max zoom) of both zoom levels
double zoom = Math.min(lngZoom, latZoom);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.