【问题标题】:How does this Google Maps zoom level calculation work?这个谷歌地图缩放级别计算是如何工作的?
【发布时间】:2011-08-21 20:25:21
【问题描述】:

我知道输入和输出是什么,但我只是不确定它是如何工作或为什么工作的。

此代码用于在给定包含一组点的最小和最大经度/纬度(正方形)的情况下确定 Google 地图上仍将显示所有这些点的最大缩放级别。原作者已经走了,所以我不确定其中一些数字是什么(即 6371 和 8)。把它当作一个谜题=D

int mapdisplay = 322; //min of height and width of element which contains the map
double dist = (6371 * Math.acos(Math.sin(min_lat / 57.2958) * Math.sin(max_lat / 57.2958) + 
            (Math.cos(min_lat / 57.2958) * Math.cos(max_lat / 57.2958) * Math.cos((max_lon / 57.2958) - (min_lon / 57.2958)))));

double zoom = Math.floor(8 - Math.log(1.6446 * dist / Math.sqrt(2 * (mapdisplay * mapdisplay))) / Math.log (2));

if(numPoints == 1 || ((min_lat == max_lat)&&(min_lon == max_lon))){
    zoom = 11;
}

【问题讨论】:

标签: java algorithm google-maps latitude-longitude


【解决方案1】:

有些数字很容易解释

再一次,缩放级别每一步都会使大小翻倍,即将缩放级别增加屏幕大小的一半。

zoom = 8 - log(factor * dist) / log(2) = 8 - log_2(factor * dist)
=> dist = 2^(8-zoom) / factor

从数字中我们发现缩放级别 8 对应的距离为 276.89 公里。

【讨论】:

  • 假设我想将 GeoFence 的默认半径设为 2.5 英里,最小半径为 500 米,最大半径为 25 英里,那么我该怎么做,我知道我需要在 onMove 上提供动态缩放级别来映射() 方法,但值如何以及将是什么!
【解决方案2】:

我使用下面的一个简单公式:

public int getZoomLevel(Circle circle) {
    if (circle != null){
        double radius = circle.getRadius();
        double scale = radius / 500;
        zoomLevel =(int) (16 - Math.log(scale) / Math.log(2));
    }
    return zoomLevel;
}

你也可以用它的特定半径替换圆。

【讨论】:

  • 新的谷歌地图似乎使用直径值而不是缩放。我似乎能够使用您的函数将此直径转换为旧的缩放值(也用于地图嵌入),但使用 1000 而不是 500(当然,因为半径 = 直径 / 2)和 19 而不是 16(最大缩放似乎是 19)。我用几个值对其进行了测试。见:gist.github.com/panzi/6694200
  • 感谢这对我有用并给出准确的结果 +1
  • 在 720x1080 像素 XHDPI 屏幕上半径为 2500,结果不正确。
  • @RahulRastogi,在这里查看我的答案:stackoverflow.com/questions/29895875/…
  • @panzi,目前googleMap.getMaxZoomLevel() = 21。你认为我们为什么应该除以1000?我想这取决于屏幕。
【解决方案3】:

This page 非常有助于解释所有这些内容(两个 lat-lng 对之间的距离等)。

6371 是地球的近似半径,以千米为单位。

57.2958 是 180/pi

另外,查看这些墨卡托投影计算,以在纬度-经度和 X-Y 之间进行转换:http://wiki.openstreetmap.org/wiki/Mercator

【讨论】:

    【解决方案4】:

    经过多次尝试,我找到了解决方案。我假设您在半径之外有一个填充(例如,如果您的半径 = 10000 m,那么它的左右距离为 2500 m)。你也应该有一个米的精度。您可以使用递归(二分搜索)设置合适的缩放。如果你将moveCamera 更改为animateCamera,你会得到一个有趣的搜索动画。半径越大,您收到的缩放值就越准确。这是一个常见的二分查找。

    private fun getCircleZoomValue(latitude: Double, longitude: Double, radius: Double,
                                   minZoom: Float, maxZoom: Float): Float {
        val position = LatLng(latitude, longitude)
        val currZoom = (minZoom + maxZoom) / 2
        val camera = CameraUpdateFactory.newLatLngZoom(position, currZoom)
        googleMap!!.moveCamera(camera)
        val results = FloatArray(1)
        val topLeft = googleMap!!.projection.visibleRegion.farLeft
        val topRight = googleMap!!.projection.visibleRegion.farRight
        Location.distanceBetween(topLeft.latitude, topLeft.longitude, topRight.latitude,
            topRight.longitude, results)
        // Difference between visible width in meters and 2.5 * radius.
        val delta = results[0] - 2.5 * radius
        val accuracy = 10 // 10 meters.
        return when {
            delta < -accuracy -> getCircleZoomValue(latitude, longitude, radius, minZoom,
                currZoom)
            delta > accuracy -> getCircleZoomValue(latitude, longitude, radius, currZoom,
                maxZoom)
            else -> currZoom
        }
    }
    

    用法:

    if (googleMap != null) {
        val zoom = getCircleZoomValue(latitude, longitude, radius, googleMap!!.minZoomLevel, 
            googleMap!!.maxZoomLevel)
    }
    

    您不应早于googleMap?.setOnCameraIdleListener 的第一个事件内调用此方法,请参阅animateCamera works and moveCamera doesn't for GoogleMap - Android。如果您在onMapReady 之后立即调用它,您将获得错误的距离,因为此时地图不会自行绘制。

    警告!缩放级别取决于位置(纬度)。因此,根据与赤道的距离,圆圈将具有不同的大小和相同的缩放级别(请参阅Determine a reasonable zoom level for Google Maps given location accuracy)。

    【讨论】:

      【解决方案5】:

      我需要相反的情况:给定特定缩放级别的特定半径(即,缩放级别 15 时的 40 米),我需要在地图中显示相同圆圈大小(以图形方式)的其他缩放级别的半径。为此:

      // after retrieving the googleMap from either getMap() or getMapAsync()...
      
      // we want a circle with r=40 meters at zoom level 15
      double base = 40 / zoomToDistFactor(15);
      
      final Circle circle = googleMap.addCircle(new CircleOptions()
              .center(center)
              .radius(40)
              .fillColor(Color.LTGRAY)
      );
      
      googleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
          @Override
          public void onCameraMove() {
              CameraPosition cameraPosition = googleMap.getCameraPosition();
              LatLng center = cameraPosition.target;
              float z2 = cameraPosition.zoom;
              double newR = base * zoomToDistFactor(z2);
              circle.setRadius(newR);
          }
      });
      
      // ...
      
      private double zoomToDistFactor(double z) {
          return Math.pow(2,8-z) / 1.6446;
      }
      

      我想我会把它放在这里是为了节省其他人获得这种转换的努力。

      作为旁注,像这样在 cameramovelistener 中移动圆圈会使圆圈运动非常不稳定。我最终在封闭的 MapView 的中心放置了一个视图,它只是画了一个小圆圈。

      【讨论】:

        【解决方案6】:

        我认为他得到了这个功能:

         function calculateZoom(WidthPixel,Ratio,Lat,Length){
            // from a segment Length (km), 
            // with size ratio of the of the segment expected on a map (70%),
            // with a map widthpixel size (100px), and a latitude (45°) we can ge the best Zoom
            // earth radius : 6,378,137m, earth is a perfect ball; perimeter at the equator = 40,075,016.7 m
            // the full world on googlemap is available in a box of 256 px; It has a ratio of 156543.03392 (px/m)
            // for Z = 0; 
            // pixel scale at the Lat_level is ( 156543,03392 * cos ( PI * (Lat/180) ))
            // map scale increase at the rate of square root of Z
            //
            Length = Length *1000;                     //Length is in Km
            var k = WidthPixel * 156543.03392 * Math.cos(Lat * Math.PI / 180);        //k = perimeter of the world at the Lat_level, for Z=0 
            var myZoom = Math.round( Math.log( (Ratio * k)/(Length*100) )/Math.LN2 );
            myZoom =  myZoom -1;                   // z start from 0 instead of 1
            //console.log("calculateZoom: width "+WidthPixel+" Ratio "+Ratio+" Lat "+Lat+" length "+Length+" (m) calculated zoom "+ myZoom);
        
            // not used but it could be usefull for some: Part of the world size a the Lat 
            MapDim = k /Math.pow(2,myZoom);
            //console.log("calculateZoom: size of the map at the Lat: "+MapDim + " meters.");
            //console.log("calculateZoom: world perimeter at the Lat: " +k+ " meters.");
        return(myZoom);
        }
        

        【讨论】:

        • 欢迎来到 Stack Overflow!虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!
        猜你喜欢
        • 2012-04-17
        • 2012-09-08
        • 2011-09-29
        • 2011-05-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多