首页 文章

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

提问于
浏览
124

我正在寻找一种使用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()方法用于我的特定用例 . 它适用于在 Map 上拟合标记,但它不适用于设置精确边界 . 这是一个为什么我不能使用fitBounds()方法的例子 .

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

8 回答

  • 234

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

    谷歌集团也提出了类似的问题:http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

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

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

    对于如何拟合精确边界没有简单的答案,因为即使你愿意改变 Map div的大小,你也必须选择你改变的大小和相应的缩放级别(粗略地说,你是否更大或更小)比现在的?) .

    如果你真的需要计算缩放,而不是存储它,这应该可以解决问题:

    墨卡托投影会扭曲纬度,但经度的任何差异始终代表 Map 宽度的相同部分(角度差以度/ 360为单位) . 在缩放零点处,整个世界 Map 为256x256像素,并且每个级别的缩放都是宽度和高度的两倍 . 所以在一个小代数之后,我们可以如下计算变焦,前提是我们知道 Map 的宽度(以像素为单位) . 请注意,因为经度缠绕,我们必须确保角度为正 .

    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

    ES6 上使用react-google-maps查找平均默认中心的工作示例:

    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>
    
  • 31

    由于所有其他答案似乎对我有一个或另一组情况( Map 宽度/高度,边界宽度/高度等)有问题我想我在这里把我的答案...

    这里有一个非常有用的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;
       }
    
    };
    

    如果需要,你当然可以清理它或缩小它,但我保留变量名长,以便更容易理解 .

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

  • 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 .

  • 98

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

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

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

    你必须首先检查角度2是否大于角度,然后在负数下加上360 .

    否则你总是拥有比角度更大的 Value

    所以正确的解决方案是:

    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

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

    在jsfiddle上演示

    参数:

    "bounds"参数值应为 google.maps.LatLngBounds 对象 .

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

    如果您使用的是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 Map 使用墨卡托投影 . 在墨卡托投影中,经度线的间距相等,但纬线不是 . 纬度线之间的距离随着它们从赤道到极点的增加而增加 . 实际上,当距离到达极点时,距离趋于无穷大 . 但是,谷歌 Map Map 并未显示北纬约85度以上或约低于-85度的纬度 . (reference)(我在/-85.05112877980658度计算实际截止值 . )

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

    其他说明:

    • 缩放级别范围从0到最大缩放级别 . 缩放级别0是完全缩小的 Map . 更高级别进一步缩放 Map . (reference

    • 在缩放级别0时,整个世界可以显示在256 x 256像素的区域中 . (reference

    • 对于每个更高的缩放级别,显示相同区域所需的像素数在宽度和高度上都会翻倍 . (reference

    • Map 沿纵向包裹,但不在纬度方向 .

相关问题