Google Maps V3 - 如何计算给定边界的缩放级别

Nic*_*ark 130 google-maps google-maps-api-3

我正在寻找一种使用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
Run Code Online (Sandbox Code Playgroud)

不幸的是,我无法将该fitBounds()方法用于我的特定用例.它适用于在地图上拟合标记,但它不适用于设置精确边界.这是我不能使用该fitBounds()方法的一个例子.

map.fitBounds(map.getBounds()); // not what you expect
Run Code Online (Sandbox Code Playgroud)

Joh*_*n S 260

感谢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);
}
Run Code Online (Sandbox Code Playgroud)

Demo on jsfiddle

参数:

"bounds"参数值应该是一个google.maps.LatLngBounds对象.

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

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

var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };
Run Code Online (Sandbox Code Playgroud)

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

var mapDim = $('mapElementId').getDimensions();
Run Code Online (Sandbox Code Playgroud)

返回值:

返回值是仍将显示整个边界的最大缩放级别.此值将介于0最大缩放级别和最大缩放级别之间.

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


说明:

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

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

其他说明:

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

  • 优秀的答案,这应该是最高投票,因为它既考虑了经度,也考虑了纬度.到目前为止工作得很好. (6认同)

Gil*_*dam 102

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

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

另一个问题是边长之间的比例,例如,您无法将边界精确地拟合到方形图内的细长矩形.

对于如何拟合精确边界没有简单的答案,因为即使你愿意改变地图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);
Run Code Online (Sandbox Code Playgroud)

  • pixelWidth的值是多少 (17认同)
  • 通过将Math.round更改为Math.floor,对我来说非常有用.太感谢了. (3认同)
  • 如果不考虑纬度,这怎么可能是正确的?在赤道附近它应该没问题但是给定缩放级别的地图比例会根据纬度而变化! (3认同)

d.r*_*aev 36

对于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);
Run Code Online (Sandbox Code Playgroud)

和一些可选的技巧:

//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);
}
Run Code Online (Sandbox Code Playgroud)

  • @Kush,好点。但 `maxZoom` 会阻止用户**手动**缩放。我的示例仅在必要时更改 DefaultZoom。 (3认同)

ikh*_*san 8

飞镖版本:

  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);
  }
Run Code Online (Sandbox Code Playgroud)


小智 5

这是该函数的 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) / 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)
    }
Run Code Online (Sandbox Code Playgroud)


小智 5

没有一个得到高度评价的答案对我有用。他们抛出了各种未定义的错误,最终计算出角度的 inf/nan。我怀疑 LatLngBounds 的行为可能随着时间的推移而改变。无论如何,我发现这段代码可以满足我的需求,也许它可以帮助某人:

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)

}
Run Code Online (Sandbox Code Playgroud)