Android:如何将地图视图的缩放级别设置为当前位置周围 1 公里半径?

Posted

技术标签:

【中文标题】Android:如何将地图视图的缩放级别设置为当前位置周围 1 公里半径?【英文标题】:Android: How do I set the zoom level of map view to 1 km radius around my current location? 【发布时间】:2011-08-25 13:04:39 【问题描述】:

我想将地图视图设置为半径为 1 公里,但不知道如何设置?

文档说缩放级别 1 会将地球赤道映射到 256 像素。那么如何计算需要设置的缩放级别,以便地图视图以 1KM 为半径显示区域?

更新: 在阅读了几篇博文后,我编写了以下代码:

private int calculateZoomLevel() 
    double equatorLength = 6378140; // in meters
    double widthInPixels = screenWidth;
    double metersPerPixel = equatorLength / 256;
    int zoomLevel = 1;
    while ((metersPerPixel * widthInPixels) > 2000) 
        metersPerPixel /= 2;
        ++zoomLevel;
    
    Log.i("ADNAN", "zoom level = "+zoomLevel);
    return zoomLevel;

这个想法是,首先我在缩放级别 1 中计算 每像素米,根据谷歌的说法,它使用 256 像素显示地球的赤道。现在每个后续缩放级别都会放大 2 级,因此对于每个缩放级别,我将每个像素的米减半。我这样做直到我有一个缩放级别,其中 每像素米 乘以 屏幕宽度 得到小于 2000 即 2 公里宽。

但我不认为我得到的缩放级别是显示 2 公里半径的地图。有人可以告诉我我在这里做错了什么吗?

【问题讨论】:

你的想法没问题,但问题是你设置的equatorLength,正确的值在40075004米左右。 (***) 【参考方案1】:

虽然这个答案是合乎逻辑的,我发现它有效,但结果不准确,我不知道为什么,但我厌倦了这种方法,而且这种技术更准确。

1) 在具有所需半径的物体上画一个圆

Circle circle = mGoogleMap.addCircle(new CircleOptions().center(new LatLng(latitude, longitude)).radius(getRadiusInMeters()).strokeColor(Color.RED));           
        circle.setVisible(true);
        getZoomLevel(circle);

2) 将该对象传递给此函数并设置缩放级别 这里是a link

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;

【讨论】:

正是我所需要的。我发现 ios 库更有效地处理需要在地图上显示的内容 不错的计算,我自己进行了更改,以便它准确...` public static float zoomLevel(Circle circle) float zoomLevel = 15; if (circle != null) 双半径 = circle.getRadius();双刻度=半径/500; zoomLevel =(float) (16 - Math.log(scale) / Math.log(2)); Log.i(TAG, "缩放级别 = " + zoomLevel ); 返回 zoomLevel - 0.5f ; ` 超出了 0.5f,float 是返回值,因为缩放级别也是浮动的。 500 的基础是什么? 但实际上半径是已知的。那么,我们真的需要一个圈子吗?【参考方案2】:

以下代码是最终使用的。给定屏幕宽度以及在缩放级别 1 时地球的赤道长度为 256 像素这一事实,并且每个后续缩放级别都会使表示地球赤道所需的像素数翻倍,以下函数返回屏幕将显示区域的缩放级别2公里宽。

private int calculateZoomLevel(int screenWidth) 
    double equatorLength = 40075004; // in meters
    double widthInPixels = screenWidth;
    double metersPerPixel = equatorLength / 256;
    int zoomLevel = 1;
    while ((metersPerPixel * widthInPixels) > 2000) 
        metersPerPixel /= 2;
        ++zoomLevel;
    
    Log.i("ADNAN", "zoom level = "+zoomLevel);
    return zoomLevel;

【讨论】:

developers.google.com/maps/documentation/javascript/… 这可能表明缩放级别 1 的地图有 256x256 像素。是这个原因吗? @Inn_vita:您可以简单地将值 2000 更改为 20000 即,而不是使用 "while ((metersPerPixel * widthInPixels) > 2000)" 使用 "while ((metersPerPixel * widthInPixels) > 20000)跨度> 好吧,不可能精确到 1 公里。在每次迭代中,可见的减少到一半。因此,只要它大于 2000,它的一半就会大于 1000,但如果它低于 2000,那么一半就会小于 1000,这是不可取的。所以我使用等于或大于 1 公里半径的最小缩放值。 嗯,我想这可行,但是缩放级别现在可以是浮动的,所以你真正想要的是缩放到跨度,半径为中心点的 ± lat lng 我还想指出,根据参考文档,它是 256dp 而不是 256px。如上所述,这将在不同密度的设备上产生不同的结果。当然,除非传入正确的 dp 数量而不是 px。【参考方案3】:

我最终使用了以下工具:

https://github.com/googlemaps/android-maps-utils

我从库中提取了类,因此您不需要整个库。 您无需设置缩放级别,而是使用边界。结果是一样的。

精确显示 1 公里的代码:

animateToMeters(1000);

private void animateToMeters(int meters)
    int mapHeightInDP = 200;
    Resources r = getResources();
    int mapSideInPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mapHeightInDP, r.getDisplayMetrics());

    LatLng point = new LatLng(0, 0);
    LatLngBounds latLngBounds = calculateBounds(point, meters);
    if(latLngBounds != null)
        cameraUpdate = CameraUpdateFactory.newLatLngBounds(latLngBounds, mapSideInPixels, mapSideInPixels, MARKER_BOUNDS);
        if(mMap != null)
            mMap.animateCamera(cameraUpdate); 
    


private LatLngBounds calculateBounds(LatLng center, double radius) 
    return new LatLngBounds.Builder().
      include(SphericalUtil.computeOffset(center, radius, 0)).
      include(SphericalUtil.computeOffset(center, radius, 90)).
      include(SphericalUtil.computeOffset(center, radius, 180)).
      include(SphericalUtil.computeOffset(center, radius, 270)).build();

从库中提取(略有更改)的类:

public class SphericalUtil 

    static final double EARTH_RADIUS = 6371009;

    /**
     * Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit sphere.
     */
    static double havDistance(double lat1, double lat2, double dLng) 
        return hav(lat1 - lat2) + hav(dLng) * cos(lat1) * cos(lat2);
    

    /**
     * Returns haversine(angle-in-radians).
     * hav(x) == (1 - cos(x)) / 2 == sin(x / 2)^2.
     */
    static double hav(double x) 
        double sinHalf = sin(x * 0.5);
        return sinHalf * sinHalf;
    

    /**
     * Computes inverse haversine. Has good numerical stability around 0.
     * arcHav(x) == acos(1 - 2 * x) == 2 * asin(sqrt(x)).
     * The argument must be in [0, 1], and the result is positive.
     */
    static double arcHav(double x) 
        return 2 * asin(sqrt(x));
    

    private SphericalUtil() 

    /**
     * Returns the heading from one LatLng to another LatLng. Headings are
     * expressed in degrees clockwise from North within the range [-180,180).
     * @return The heading in degrees clockwise from north.
     */
    public static double computeHeading(LatLng from, LatLng to) 
        // http://williams.best.vwh.net/avform.htm#Crs
        double fromLat = toRadians(from.latitude);
        double fromLng = toRadians(from.longitude);
        double toLat = toRadians(to.latitude);
        double toLng = toRadians(to.longitude);
        double dLng = toLng - fromLng;
        double heading = atan2(
                sin(dLng) * cos(toLat),
                cos(fromLat) * sin(toLat) - sin(fromLat) * cos(toLat) * cos(dLng));
        return wrap(toDegrees(heading), -180, 180);
    

    /**
     * Returns the LatLng resulting from moving a distance from an origin
     * in the specified heading (expressed in degrees clockwise from north).
     * @param from     The LatLng from which to start.
     * @param distance The distance to travel.
     * @param heading  The heading in degrees clockwise from north.
     */
    public static LatLng computeOffset(LatLng from, double distance, double heading) 
        distance /= EARTH_RADIUS;
        heading = toRadians(heading);
        // http://williams.best.vwh.net/avform.htm#LL
        double fromLat = toRadians(from.latitude);
        double fromLng = toRadians(from.longitude);
        double cosDistance = cos(distance);
        double sinDistance = sin(distance);
        double sinFromLat = sin(fromLat);
        double cosFromLat = cos(fromLat);
        double sinLat = cosDistance * sinFromLat + sinDistance * cosFromLat * cos(heading);
        double dLng = atan2(
                sinDistance * cosFromLat * sin(heading),
                cosDistance - sinFromLat * sinLat);
        return new LatLng(toDegrees(asin(sinLat)), toDegrees(fromLng + dLng));
    

    /**
     * Returns the location of origin when provided with a LatLng destination,
     * meters travelled and original heading. Headings are expressed in degrees
     * clockwise from North. This function returns null when no solution is
     * available.
     * @param to       The destination LatLng.
     * @param distance The distance travelled, in meters.
     * @param heading  The heading in degrees clockwise from north.
     */
    public static LatLng computeOffsetOrigin(LatLng to, double distance, double heading) 
        heading = toRadians(heading);
        distance /= EARTH_RADIUS;
        // http://lists.maptools.org/pipermail/proj/2008-October/003939.html
        double n1 = cos(distance);
        double n2 = sin(distance) * cos(heading);
        double n3 = sin(distance) * sin(heading);
        double n4 = sin(toRadians(to.latitude));
        // There are two solutions for b. b = n2 * n4 +/- sqrt(), one solution results
        // in the latitude outside the [-90, 90] range. We first try one solution and
        // back off to the other if we are outside that range.
        double n12 = n1 * n1;
        double discriminant = n2 * n2 * n12 + n12 * n12 - n12 * n4 * n4;
        if (discriminant < 0) 
            // No real solution which would make sense in LatLng-space.
            return null;
        
        double b = n2 * n4 + sqrt(discriminant);
        b /= n1 * n1 + n2 * n2;
        double a = (n4 - n2 * b) / n1;
        double fromLatRadians = atan2(a, b);
        if (fromLatRadians < -PI / 2 || fromLatRadians > PI / 2) 
            b = n2 * n4 - sqrt(discriminant);
            b /= n1 * n1 + n2 * n2;
            fromLatRadians = atan2(a, b);
        
        if (fromLatRadians < -PI / 2 || fromLatRadians > PI / 2) 
            // No solution which would make sense in LatLng-space.
            return null;
        
        double fromLngRadians = toRadians(to.longitude) -
                atan2(n3, n1 * cos(fromLatRadians) - n2 * sin(fromLatRadians));
        return new LatLng(toDegrees(fromLatRadians), toDegrees(fromLngRadians));
    

    /**
     * Returns the LatLng which lies the given fraction of the way between the
     * origin LatLng and the destination LatLng.
     * @param from     The LatLng from which to start.
     * @param to       The LatLng toward which to travel.
     * @param fraction A fraction of the distance to travel.
     * @return The interpolated LatLng.
     */
    public static LatLng interpolate(LatLng from, LatLng to, double fraction) 
        // http://en.wikipedia.org/wiki/Slerp
        double fromLat = toRadians(from.latitude);
        double fromLng = toRadians(from.longitude);
        double toLat = toRadians(to.latitude);
        double toLng = toRadians(to.longitude);
        double cosFromLat = cos(fromLat);
        double cosToLat = cos(toLat);

        // Computes Spherical interpolation coefficients.
        double angle = computeAngleBetween(from, to);
        double sinAngle = sin(angle);
        if (sinAngle < 1E-6) 
            return from;
        
        double a = sin((1 - fraction) * angle) / sinAngle;
        double b = sin(fraction * angle) / sinAngle;

        // Converts from polar to vector and interpolate.
        double x = a * cosFromLat * cos(fromLng) + b * cosToLat * cos(toLng);
        double y = a * cosFromLat * sin(fromLng) + b * cosToLat * sin(toLng);
        double z = a * sin(fromLat) + b * sin(toLat);

        // Converts interpolated vector back to polar.
        double lat = atan2(z, sqrt(x * x + y * y));
        double lng = atan2(y, x);
        return new LatLng(toDegrees(lat), toDegrees(lng));
    

    /**
     * Returns distance on the unit sphere; the arguments are in radians.
     */
    private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) 
        return arcHav(havDistance(lat1, lat2, lng1 - lng2));
    

    /**
     * Returns the angle between two LatLngs, in radians. This is the same as the distance
     * on the unit sphere.
     */
    static double computeAngleBetween(LatLng from, LatLng to) 
        return distanceRadians(toRadians(from.latitude), toRadians(from.longitude),
                               toRadians(to.latitude), toRadians(to.longitude));
    

    /**
     * Returns the distance between two LatLngs, in meters.
     */
    public static double computeDistanceBetween(LatLng from, LatLng to) 
        return computeAngleBetween(from, to) * EARTH_RADIUS;
    

    /**
     * Returns the length of the given path, in meters, on Earth.
     */
    public static double computeLength(List<LatLng> path) 
        if (path.size() < 2) 
            return 0;
        
        double length = 0;
        LatLng prev = path.get(0);
        double prevLat = toRadians(prev.latitude);
        double prevLng = toRadians(prev.longitude);
        for (LatLng point : path) 
            double lat = toRadians(point.latitude);
            double lng = toRadians(point.longitude);
            length += distanceRadians(prevLat, prevLng, lat, lng);
            prevLat = lat;
            prevLng = lng;
        
        return length * EARTH_RADIUS;
    

    /**
     * Returns the area of a closed path on Earth.
     * @param path A closed path.
     * @return The path's area in square meters.
     */
    public static double computeArea(List<LatLng> path) 
        return abs(computeSignedArea(path));
    

    /**
     * Returns the signed area of a closed path on Earth. The sign of the area may be used to
     * determine the orientation of the path.
     * "inside" is the surface that does not contain the South Pole.
     * @param path A closed path.
     * @return The loop's area in square meters.
     */
    public static double computeSignedArea(List<LatLng> path) 
        return computeSignedArea(path, EARTH_RADIUS);
    

    /**
     * Returns the signed area of a closed path on a sphere of given radius.
     * The computed area uses the same units as the radius squared.
     * Used by SphericalUtilTest.
     */
    static double computeSignedArea(List<LatLng> path, double radius) 
        int size = path.size();
        if (size < 3)  return 0; 
        double total = 0;
        LatLng prev = path.get(size - 1);
        double prevTanLat = tan((PI / 2 - toRadians(prev.latitude)) / 2);
        double prevLng = toRadians(prev.longitude);
        // For each edge, accumulate the signed area of the triangle formed by the North Pole
        // and that edge ("polar triangle").
        for (LatLng point : path) 
            double tanLat = tan((PI / 2 - toRadians(point.latitude)) / 2);
            double lng = toRadians(point.longitude);
            total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng);
            prevTanLat = tanLat;
            prevLng = lng;
        
        return total * (radius * radius);
    

    /**
     * Returns the signed area of a triangle which has North Pole as a vertex.
     * Formula derived from "Area of a spherical triangle given two edges and the included angle"
     * as per "Spherical Trigonometry" by Todhunter, page 71, section 103, point 2.
     * See http://books.google.com/books?id=3uBHAAAAIAAJ&pg=PA71
     * The arguments named "tan" are tan((pi/2 - latitude)/2).
     */
    private static double polarTriangleArea(double tan1, double lng1, double tan2, double lng2) 
        double deltaLng = lng1 - lng2;
        double t = tan1 * tan2;
        return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng));
    

    /**
     * Wraps the given value into the inclusive-exclusive interval between min and max.
     * @param n   The value to wrap.
     * @param min The minimum.
     * @param max The maximum.
     */
    static double wrap(double n, double min, double max) 
        return (n >= min && n < max) ? n : (mod(n - min, max - min) + min);
    

    /**
     * Returns the non-negative remainder of x / m.
     * @param x The operand.
     * @param m The modulus.
     */
    static double mod(double x, double m) 
        return ((x % m) + m) % m;
    

【讨论】:

嗨,mapSideInPixels,你能告诉我在哪里可以找到它吗? 我更改了我的代码更新了我的代码:int mapSideInPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mapHeightInDP, r.getDisplayMetrics()); 你能告诉我你对MARKER_BOUNDS的定义吗?非常感谢! 它只是标记和地图之间的边界之间的空间。这真的无所谓。考虑投票给我的答案,让其他人注意到答案! 嗨@Oritm,我可以从这个答案中获得一些信息以满足我的需要。我想为“X”缩放级别的半径计算设备上的像素数。基本上我在地图上覆盖了自己的圆圈来创建区域。现在我想根据缩放级别调整圆圈的宽度。你能帮我一个线索吗?【参考方案4】:

Google 地图似乎与英里/像素密切相关。在缩放 = 13 时,1 英里 = 100 像素。 2 英里 = 200 像素。每个缩放 leven 增加或减少 2 倍。因此,在缩放 14 时,1 英里 = 200 像素,在缩放 12 时,1 英里 = 50 像素。

【讨论】:

嗨@Thomas,我想计算“X”缩放级别半径的设备上的像素数。基本上我在地图上覆盖了自己的圆圈来创建区域。现在我想根据缩放级别调整圆圈的宽度。你能帮我一个线索吗?【参考方案5】:

我已将接受的答案转换为返回双精度值,因为 Android 谷歌地图库使用浮点缩放级别,并且还考虑了远离赤道的纬度。

public static double getZoomForMetersWide (
  final double desiredMeters,
  final double mapWidth,
  final double latitude )

  final double latitudinalAdjustment = Math.cos( Math.PI * latitude / 180.0 );

  final double arg = EQUATOR_LENGTH * mapWidth * latitudinalAdjustment / ( desiredMeters * 256.0 );

  return Math.log( arg ) / Math.log( 2.0 );

顺便说一句,为了在 Android 上获得最佳效果,不要传递视图的实际像素数,而是根据设备的像素密度缩放尺寸。

DisplayMetrics metrics = getResources().getDisplayMetrics();
float mapWidth = mapView.getWidth() / metrics.scaledDensity;

希望这对某人有所帮助。

【讨论】:

什么是EQUATOR_LENGTH 赤道长度,米,约6378140 mapWidth 是否需要以像素或 dp 为单位。 应该使用metrics.density,而不是metrics.scaledDensityscaledDensity 用于缩放文本。 EQUATOR_LENGTH = 40075004metrics.density 完美合作,谢谢@sho 你救了我的一天【参考方案6】:

使用循环计算缩放级别非常幼稚。 使用数学要好得多。

这里是函数(返回类型:float)

public static double calcZoom(int visible_distance, int img_width)

    // visible_distance -> in meters
    // img_width -> in pixels

    visible_distance = Math.abs(visible_distance);
    double equator_length = 40075016; // in meters

    // for an immage of 256 pixel pixel
    double zoom256 = Math.log(equator_length/visible_distance)/Math.log(2);

    // adapt the zoom to the image size
    int x = (int) (Math.log(img_width/256)/Math.log(2));
    double zoom = zoom256 + x;

    return zoom;

示例调用:

public static void main(String[] args)

    // computes the zoom for 1km=1000m for an image having 256 width
    double zoom = MainClass.calcZoom(1000, 256);
    System.out.println("zoom: " + String.valueOf(zoom));
    return;
 

计算缩放级别的数学公式是:

equator_length = 40075016
zoom_level = logE(equator_length/distance)/logE(2) + logE(img_width/256)/logE(2)
// The zoom_level computed here is a float number.

就是这样! :-)

注意:以上作为公认答案的解决方案仅适用于赤道附近的缩放级别。 如果您想要一个适用于所有纬度的解决方案,您需要在要计算的缩放级别的相同纬度处的平行线长度。 calcZoom 方法更改为

private double calcZoom(int visible_distance, int img_width, double atLatitude) 
    // visible_distance -> in meters
    // img_width -> in pixels

    double parallel_length = this.calcParallelLegth(atLatitude); // in meters

    // for an immage of 256 pixel pixel
    zoom256 = Math.log(parallel_length/visible_distance))/Math.log(2)

    // adapt the zoom to the image size
    x = (int) Math.log(img_width/256)/Math.log(2)
    zoom = zoom256 + x

    return zoom;

其中this.calcParallelLegth(atLatitude) 返回atLatitude 纬度处的平行线长度。

您可以使用一些库自己计算长度(最好使用 Vincenty 公式)。

或者

如果您没有这样的库(或者您没有搜索库,或者您只是想要一个完整的代码)在这个答案的底部,您可以找到整个工作代码 使用double calcParallelLegth(double atLatitude) 的实现,它使用一个表(使用文森特公式计算),在所有纬度具有平行长度,容差为 3%。


注意:只有在您理解公式时才需要阅读以下内容(或检查公式是否正确)

公式说明如下:

简单地说!

让我们把问题分成两部分。

第 1 部分 计算 256x256 尺寸图像的缩放比例

第 2 部分 调整缩放以适应不同尺寸的图像

解决第 1 部分 图像尺寸为 256x256。 缩放级别 0 显示整个赤道。 每个后续缩放级别都让我看到之前的一半。

赤道长 40,075,016 米(根据 WGS-84 (*1) 和 文森提公式(*2))

zoom=0 -> 40,075,016 / 1   = 40,075,016 meters visible         Note: 2^0=1
zoom=1 -> 40,075,016 / 2   = 20,037,508 meters visible         Note: 2^1=2
zoom=2 -> 40,075,016 / 4   = 10,018,754 meters visible         Note: 2^2=4
zoom=3 -> 40,075,016 / 8   =  5,009,377 meters visible         Note: 2^3=8
zoom=4 -> 40,075,016 / 16  =  2,504,688.5 meters visible       Note: 2^4=16
zoom=5 -> 40,075,016 / 2^5 =  1,252,344.25 meters visible      Note= 2^5=32
zoom=6 -> 40,075,016 / 2^6 =    636,172.125 meters visible     Note= 2^6=64
... 
zoom   -> equator_length / 2^zoom = visible_distance

正如您在上面看到的,每个后续缩放级别都让我看到之前的一半。

zoom 是所需的缩放级别。visible_distance 是图像水平显示的米数。

如果您想要 1 公里,则必须使用 visible_distance=1000

计算 zoom

让我们找出缩放公式。 这就是数学的神奇之处(“无聊”的神奇东西)。

   equator_length / 2^zoom = visible_distance ->                            
-> equator_length / visible_distance = 2^zoom ->
-> log2(equator_length / visible_distance) = log2(2^zoom) ->        (*3)
-> log2(equator_length / visible_distance) = zoom*log2(2) ->        (*4)
-> log2(equator_length / visible_distance) = zoom*1 ->              (*5)
-> log2(equator_length / visible_distance) = zoom ->
-> logE(equator_length / visible_distance)/logE(2) = zoom ->          (*6)

256x256 图像的缩放级别公式为:

zoom256 = logE(equator_length/visible_distance) / logE(2)

第 1 部分完成!!

解决第 2 部分

将缩放调整为所需的图像大小。

图像宽度每增加一倍,查看整个赤道所需的缩放比例就会增加一倍。

示例: 在 512x512 的图像中,查看整个赤道所需的缩放比例为 1。 在 1024x1024 的图像中,查看整个赤道所需的缩放为 2。 在 2048x2048 的图像中,查看整个赤道所需的缩放比例为 3。

说的是

width= 256 ->  256/256 = 1 ->   zoom=0 (needed to see the whole equator)
width= 512 ->  512/256 = 2   -> zoom=1 (needed to see the whole equator)
width=1024 -> 1024/256 = 4   -> zoom=2 (needed to see the whole equator)
width=2048 -> 2048/256 = 8   -> zoom=3 (needed to see the whole equator)
width=4096 -> 4096/256 = 2^4 -> zoom=4 (needed to see the whole equator)
width=4096 -> 4096/256 = 2^5 -> zoom=5 (needed to see the whole equator)

... width -> width/256 = 2^x -> zoom=x(需要看到整个赤道)

这意味着 (zoom_level 是

- with an   512x512    image, the zoom needed is zoom256+1
- with an  1024x1024   image, the zoom needed is zoom256+2
- with an  2048x2048   image, the zoom needed is zoom256+3
...
- with an WIDTHxHEIGHT image, the zoom needed is zoom256+x

如果需要 x 来调整缩放到想要的图像大小。

所以,这是从

中提取x的问题
width/256 = 2^x

让我们开始吧

width/256 = 2^x ->
-> log2(width/256) = log2(2^x) ->            (*3)
-> log2(width/256) = x * log2(2) ->          (*4)
-> log2(width/256) = x * 1 ->                (*5)
-> log2(width/256) = x -> 
-> logE(width/256) / logE(2) = x ->          (*6)

现在我们有了 x 公式。

WIDTHxHEIGHT 图像的缩放级别公式为:

zoom = zoom256 + x

所以,如果你想在 512x512 的图像中看到 1 公里而不是

zoom256 = logE(40075016/1000) / logE(2) = 15.29041547592718
x = logE(512/256) / logE(2) = 1
zoom = zoom256 + z = 15.29041547592718 + 1 = 16.29041547592718

如果必须是整数

zoom = floor(zoom) = 16

完成!

(*3) expr1=expr2 <-> log(expr1)=log(expr2)
(*4) logN(a^b) = b * logN(a)
(*5) logN(N) = 1
(*6) logN(expr) = log(expr)/log(N)
(*7) log(a/b) = log(a) - log(b)

这是计算每个纬度图像宽度的缩放级别的完整代码。

class MainClass

    public static int getParallelLength(double atLatitude)
    

        int FR_LAT = 0; // from latitude
        int TO_LAT = 1; // to latidude
        int PA_LEN = 2; // parallel length in meters)
        int PC_ERR = 3; // percentage error

        //  fr_lat| to_lat            |  par_len| perc_err
        double tbl[][] = 
             0.00, 12.656250000000000, 40075016, 2.410,
            12.66, 17.402343750000000, 39107539, 2.180,
            17.40, 22.148437500000000, 38252117, 2.910,
            22.15, 25.708007812500000, 37135495, 2.700,
            25.71, 28.377685546875000, 36130924, 2.330,
            28.38, 31.047363281250000, 35285940, 2.610,
            31.05, 33.717041015625000, 34364413, 2.890,
            33.72, 35.719299316406250, 33368262, 2.380,
            35.72, 37.721557617187500, 32573423, 2.560,
            37.72, 39.723815917968750, 31738714, 2.750,
            39.72, 41.726074218750000, 30865121, 2.950,
            41.73, 43.227767944335938, 29953681, 2.360,
            43.23, 44.729461669921875, 29245913, 2.480,
            44.73, 46.231155395507812, 28517939, 2.620,
            46.23, 47.732849121093750, 27770248, 2.760,
            47.73, 49.234542846679688, 27003344, 2.900,
            49.23, 50.360813140869141, 26217745, 2.290,
            50.36, 51.487083435058594, 25616595, 2.380,
            51.49, 52.613353729248047, 25005457, 2.480,
            52.61, 53.739624023437500, 24384564, 2.580,
            53.74, 54.865894317626953, 23754152, 2.690,
            54.87, 55.992164611816406, 23114464, 2.800,
            55.99, 57.118434906005859, 22465745, 2.920,
            57.12, 57.963137626647949, 21808245, 2.280,
            57.96, 58.807840347290039, 21309508, 2.360,
            58.81, 59.652543067932129, 20806081, 2.440,
            59.65, 60.497245788574219, 20298074, 2.520,
            60.50, 61.341948509216309, 19785597, 2.610,
            61.34, 62.186651229858398, 19268762, 2.700,
            62.19, 63.031353950500488, 18747680, 2.800,
            63.03, 63.876056671142578, 18222465, 2.900,
            63.88, 64.509583711624146, 17693232, 2.250,
            64.51, 65.143110752105713, 17293739, 2.320,
            65.14, 65.776637792587280, 16892100, 2.390,
            65.78, 66.410164833068848, 16488364, 2.460,
            66.41, 67.043691873550415, 16082582, 2.530,
            67.04, 67.677218914031982, 15674801, 2.610,
            67.68, 68.310745954513550, 15265074, 2.690,
            68.31, 68.944272994995117, 14853450, 2.780,
            68.94, 69.577800035476685, 14439980, 2.870,
            69.58, 70.211327075958252, 14024715, 2.970,
            70.21, 70.686472356319427, 13607707, 2.300,
            70.69, 71.161617636680603, 13293838, 2.360,
            71.16, 71.636762917041779, 12979039, 2.430,
            71.64, 72.111908197402954, 12663331, 2.500,
            72.11, 72.587053477764130, 12346738, 2.570,
            72.59, 73.062198758125305, 12029281, 2.640,
            73.06, 73.537344038486481, 11710981, 2.720,
            73.54, 74.012489318847656, 11391862, 2.800,
            74.01, 74.487634599208832, 11071946, 2.890,
            74.49, 74.962779879570007, 10751254, 2.980,
            74.96, 75.319138839840889, 10429810, 2.310,
            75.32, 75.675497800111771, 10188246, 2.370,
            75.68, 76.031856760382652,  9946280, 2.430,
            76.03, 76.388215720653534,  9703923, 2.500,
            76.39, 76.744574680924416,  9461183, 2.560,
            76.74, 77.100933641195297,  9218071, 2.640,
            77.10, 77.457292601466179,  8974595, 2.710,
            77.46, 77.813651561737061,  8730766, 2.790,
            77.81, 78.170010522007942,  8486593, 2.880,
            78.17, 78.526369482278824,  8242085, 2.970,
            78.53, 78.793638702481985,  7997252, 2.290,
            78.79, 79.060907922685146,  7813420, 2.350,
            79.06, 79.328177142888308,  7629414, 2.410,
            79.33, 79.595446363091469,  7445240, 2.470,
            79.60, 79.862715583294630,  7260900, 2.540,
            79.86, 80.129984803497791,  7076399, 2.600,
            80.13, 80.397254023700953,  6891742, 2.680,
            80.40, 80.664523243904114,  6706931, 2.750,
            80.66, 80.931792464107275,  6521972, 2.830,
            80.93, 81.199061684310436,  6336868, 2.920,
            81.20, 81.399513599462807,  6151624, 2.250,
            81.40, 81.599965514615178,  6012600, 2.310,
            81.60, 81.800417429767549,  5873502, 2.360,
            81.80, 82.000869344919920,  5734331, 2.420,
            82.00, 82.201321260072291,  5595088, 2.480,
            82.20, 82.401773175224662,  5455775, 2.550,
            82.40, 82.602225090377033,  5316394, 2.620,
            82.60, 82.802677005529404,  5176947, 2.690,
            82.80, 83.003128920681775,  5037435, 2.770,
            83.00, 83.203580835834146,  4897860, 2.850,
            83.20, 83.404032750986516,  4758224, 2.930,
            83.40, 83.554371687350795,  4618528, 2.260,
            83.55, 83.704710623715073,  4513719, 2.320,
            83.70, 83.855049560079351,  4408878, 2.370,
            83.86, 84.005388496443629,  4304006, 2.430,
            84.01, 84.155727432807907,  4199104, 2.490,
            84.16, 84.306066369172186,  4094172, 2.560,
            84.31, 84.456405305536464,  3989211, 2.630,
            84.46, 84.606744241900742,  3884223, 2.700,
            84.61, 84.757083178265020,  3779207, 2.770,
            84.76, 84.907422114629298,  3674165, 2.850,
            84.91, 85.057761050993577,  3569096, 2.940,
            85.06, 85.170515253266785,  3464003, 2.270,
            85.17, 85.283269455539994,  3385167, 2.320,
            85.28, 85.396023657813203,  3306318, 2.380,
            85.40, 85.508777860086411,  3227456, 2.440,
            85.51, 85.621532062359620,  3148581, 2.500,
            85.62, 85.734286264632829,  3069693, 2.570,
            85.73, 85.847040466906037,  2990793, 2.630,
            85.85, 85.959794669179246,  2911882, 2.710,
            85.96, 86.072548871452454,  2832959, 2.780,
            86.07, 86.185303073725663,  2754025, 2.860,
            86.19, 86.298057275998872,  2675080, 2.950,
            86.30, 86.382622927703778,  2596124, 2.280,
            86.38, 86.467188579408685,  2536901, 2.330,
            86.47, 86.551754231113591,  2477672, 2.390,
            86.55, 86.636319882818498,  2418437, 2.440,
            86.64, 86.720885534523404,  2359197, 2.510,
            86.72, 86.805451186228311,  2299952, 2.570,
            86.81, 86.890016837933217,  2240701, 2.640,
            86.89, 86.974582489638124,  2181446, 2.710,
            86.97, 87.059148141343030,  2122186, 2.790,
            87.06, 87.143713793047937,  2062921, 2.870,
            87.14, 87.228279444752843,  2003652, 2.950,
            87.23, 87.291703683531523,  1944378, 2.280,
            87.29, 87.355127922310203,  1899919, 2.340,
            87.36, 87.418552161088883,  1855459, 2.390,
            87.42, 87.481976399867563,  1810996, 2.450,
            87.48, 87.545400638646242,  1766531, 2.510,
            87.55, 87.608824877424922,  1722063, 2.580,
            87.61, 87.672249116203602,  1677594, 2.650,
            87.67, 87.735673354982282,  1633122, 2.720,
            87.74, 87.799097593760962,  1588648, 2.790,
            87.80, 87.862521832539642,  1544172, 2.880,
            87.86, 87.925946071318322,  1499695, 2.960,
            87.93, 87.973514250402332,  1455215, 2.290,
            87.97, 88.021082429486341,  1421854, 2.340,
            88.02, 88.068650608570351,  1388493, 2.400,
            88.07, 88.116218787654361,  1355130, 2.460,
            88.12, 88.163786966738371,  1321766, 2.520,
            88.16, 88.211355145822381,  1288401, 2.580,
            88.21, 88.258923324906391,  1255036, 2.650,
            88.26, 88.306491503990401,  1221669, 2.730,
            88.31, 88.354059683074411,  1188302, 2.800,
            88.35, 88.401627862158421,  1154934, 2.880,
            88.40, 88.449196041242431,  1121565, 2.970,
            88.45, 88.484872175555438,  1088195, 2.290,
            88.48, 88.520548309868445,  1063167, 2.350,
            88.52, 88.556224444181453,  1038139, 2.410,
            88.56, 88.591900578494460,  1013110, 2.470,
            88.59, 88.627576712807468,   988081, 2.530,
            88.63, 88.663252847120475,   963052, 2.590,
            88.66, 88.698928981433482,   938022, 2.660,
            88.70, 88.734605115746490,   912992, 2.740,
            88.73, 88.770281250059497,   887961, 2.810,
            88.77, 88.805957384372505,   862930, 2.900,
            88.81, 88.841633518685512,   837899, 2.980,
            88.84, 88.868390619420268,   812867, 2.300,
            88.87, 88.895147720155023,   794093, 2.360,
            88.90, 88.921904820889779,   775319, 2.420,
            88.92, 88.948661921624534,   756545, 2.480,
            88.95, 88.975419022359290,   737771, 2.540,
            88.98, 89.002176123094046,   718996, 2.610,
            89.00, 89.028933223828801,   700221, 2.680,
            89.03, 89.055690324563557,   681446, 2.750,
            89.06, 89.082447425298312,   662671, 2.830,
            89.08, 89.109204526033068,   643896, 2.910,
            89.11, 89.129272351584135,   625121, 2.250,
            89.13, 89.149340177135201,   611039, 2.300,
            89.15, 89.169408002686268,   596957, 2.350,
            89.17, 89.189475828237335,   582876, 2.410,
            89.19, 89.209543653788401,   568794, 2.470,
            89.21, 89.229611479339468,   554712, 2.530,
            89.23, 89.249679304890535,   540630, 2.600,
            89.25, 89.269747130441601,   526548, 2.670,
            89.27, 89.289814955992668,   512466, 2.740,
            89.29, 89.309882781543735,   498384, 2.820,
            89.31, 89.329950607094801,   484302, 2.900,
            89.33, 89.350018432645868,   470219, 2.990,
            89.35, 89.365069301809172,   456137, 2.310,
            89.37, 89.380120170972475,   445575, 2.370,
            89.38, 89.395171040135779,   435013, 2.420,
            89.40, 89.410221909299082,   424451, 2.480,
            89.41, 89.425272778462386,   413889, 2.550,
            89.43, 89.440323647625689,   403328, 2.610,
            89.44, 89.455374516788993,   392766, 2.680,
            89.46, 89.470425385952296,   382204, 2.760,
            89.47, 89.485476255115600,   371642, 2.840,
            89.49, 89.500527124278904,   361080, 2.920,
            89.50, 89.511815276151381,   350518, 2.260,
            89.51, 89.523103428023859,   342596, 2.310,
            89.52, 89.534391579896337,   334674, 2.360,
            89.53, 89.545679731768814,   326753, 2.420,
            89.55, 89.556967883641292,   318831, 2.480,
            89.56, 89.568256035513770,   310910, 2.540,
            89.57, 89.579544187386247,   302988, 2.610,
            89.58, 89.590832339258725,   295066, 2.680,
            89.59, 89.602120491131203,   287145, 2.750,
            89.60, 89.613408643003680,   279223, 2.830,
            89.61, 89.624696794876158,   271301, 2.910,
            89.62, 89.633162908780520,   263380, 2.250,
            89.63, 89.641629022684882,   257438, 2.300,
            89.64, 89.650095136589243,   251497, 2.360,
            89.65, 89.658561250493605,   245556, 2.410,
            89.66, 89.667027364397967,   239615, 2.470,
            89.67, 89.675493478302329,   233673, 2.540,
            89.68, 89.683959592206691,   227732, 2.600,
            89.68, 89.692425706111052,   221791, 2.670,
            89.69, 89.700891820015414,   215849, 2.750,
            89.70, 89.709357933919776,   209908, 2.830,
            89.71, 89.717824047824138,   203967, 2.910,
            89.72, 89.724173633252406,   198026, 2.250,
            89.72, 89.730523218680673,   193570, 2.300,
            89.73, 89.736872804108941,   189114, 2.350,
            89.74, 89.743222389537209,   184658, 2.410,
            89.74, 89.749571974965477,   180202, 2.470,
            89.75, 89.755921560393745,   175746, 2.530,
            89.76, 89.762271145822012,   171290, 2.600,
            89.76, 89.768620731250280,   166834, 2.670,
            89.77, 89.774970316678548,   162378, 2.740,
            89.77, 89.781319902106816,   157922, 2.820,
            89.78, 89.787669487535084,   153466, 2.900,
            89.79, 89.794019072963351,   149010, 2.990,
            89.79, 89.798781262034552,   144554, 2.310,
            89.80, 89.803543451105753,   141212, 2.360,
            89.80, 89.808305640176954,   137869, 2.420,
            89.81, 89.813067829248155,   134527, 2.480,
            89.81, 89.817830018319356,   131185, 2.540,
            89.82, 89.822592207390556,   127843, 2.610,
            89.82, 89.827354396461757,   124501, 2.680,
            89.83, 89.832116585532958,   121159, 2.750,
            89.83, 89.836878774604159,   117817, 2.830,
            89.84, 89.841640963675360,   114475, 2.910,
            89.84, 89.845212605478764,   111133, 2.250,
            89.85, 89.848784247282168,   108627, 2.300,
            89.85, 89.852355889085572,   106120, 2.360,
            89.85, 89.855927530888977,   103614, 2.410,
            89.86, 89.859499172692381,   101107, 2.470,
            89.86, 89.863070814495785,    98601, 2.540,
            89.86, 89.866642456299189,    96094, 2.600,
            89.87, 89.870214098102593,    93588, 2.670,
            89.87, 89.873785739905998,    91081, 2.750,
            89.87, 89.877357381709402,    88575, 2.830,
            89.88, 89.880929023512806,    86068, 2.910,
            89.88, 89.883607754865352,    83562, 2.240,
            89.88, 89.886286486217898,    81682, 2.300,
            89.89, 89.888965217570444,    79802, 2.350,
            89.89, 89.891643948922990,    77922, 2.410,
            89.89, 89.894322680275536,    76042, 2.470,
            89.89, 89.897001411628082,    74162, 2.530,
            89.90, 89.899680142980628,    72282, 2.600,
            89.90, 89.902358874333174,    70402, 2.660,
            89.90, 89.905037605685720,    68523, 2.740,
            89.91, 89.907716337038266,    66643, 2.820,
            89.91, 89.910395068390812,    64763, 2.900,
            89.91, 89.913073799743358,    62883, 2.980,
            89.91, 89.915082848257768,    61003, 2.310,
            89.92, 89.917091896772178,    59593, 2.360,
            89.92, 89.919100945286587,    58183, 2.420,
            89.92, 89.921109993800997,    56773, 2.480,
            89.92, 89.923119042315406,    55363, 2.540,
            89.92, 89.925128090829816,    53953, 2.610,
            89.93, 89.927137139344225,    52543, 2.680,
            89.93, 89.929146187858635,    51134, 2.750,
            89.93, 89.931155236373044,    49724, 2.830,
            89.93, 89.933164284887454,    48314, 2.910,
            89.93, 89.934671071273257,    46904, 2.250,
            89.93, 89.936177857659061,    45846, 2.300,
            89.94, 89.937684644044865,    44789, 2.360,
            89.94, 89.939191430430668,    43731, 2.410,
            89.94, 89.940698216816472,    42674, 2.470,
            89.94, 89.942205003202275,    41617, 2.540,
            89.94, 89.943711789588079,    40559, 2.600,
            89.94, 89.945218575973882,    39502, 2.670,
            89.95, 89.946725362359686,    38444, 2.740,
            89.95, 89.948232148745490,    37387, 2.820,
            89.95, 89.949738935131293,    36329, 2.900
        ;

        for(int r=0; r < tbl.length; r++)
        
            double fromLat = tbl[r][FR_LAT];
            double toLat = tbl[r][TO_LAT];
            double atLat = atLatitude;

            if(fromLat <= atLat && atLat < toLat)
            
                double parallelLength = tbl[r][PA_LEN];
                return (int)parallelLength;
             
        

        return 0;
    

    public static double calcZoom(int visible_distance, int img_width, double atLat)
    
        // visible_distance -> in meters
        // img_width -> in pixels
        // atLat -> the latitude you want the zoom level

        visible_distance = Math.abs(visible_distance);
        double parallel_length = MainClass.getParallelLength(atLat); // in meters

        // for an immage of 256 pixel pixel
        double zoom256 = Math.log(parallel_length/visible_distance)/Math.log(2);

        // adapt the zoom to the image size
        int x = (int) (Math.log(img_width/256)/Math.log(2));
        double zoom = zoom256 + x;

        return zoom;
    

    public static void main(String[] args)
    
        int len;
        double zoom;

        // equator length
        len = MainClass.getParallelLength(0);
        System.out.println("parallel length at 0: " + String.valueOf(len));

        // legth parallel at latitude 89.9 (near the north pole)
        len = MainClass.getParallelLength(89.9);
        System.out.println("parallel length at 89.9: " + String.valueOf(len));

        // the zoom level needed to see 100km=100000m in a img having 
        // width 256 at equator latitude
        zoom = MainClass.calcZoom(100000, 256, 0);
        System.out.println("zoom (100km, width:256, lat:0): " + String.valueOf(zoom));

        // the zoom level needed to see 100km=100000m in a img having 
        // width 512 at equator latitude
        zoom = MainClass.calcZoom(100000, 512, 0);
        System.out.println("zoom (100km, width:512, lat:0): " + String.valueOf(zoom));

        // the zoom level needed to see 100km=100000m in a img having 
        // width 256 at latitude 60
        zoom = MainClass.calcZoom(100000, 256, 60);
        System.out.println("zoom (100km, width:256, lat:60): " + String.valueOf(zoom));

        return;
    

【讨论】:

改进了答案【参考方案7】:

最终工作解决方案:

public static void getZoomForMetersWide(GoogleMap googleMap, int mapViewWidth, LatLng latLngPoint, int desiredMeters) 
        DisplayMetrics metrics = App.getAppCtx().getResources().getDisplayMetrics();
        float mapWidth = mapViewWidth / metrics.density;

        final int EQUATOR_LENGTH = 40075004;
        final int TIME_ANIMATION_MILIS = 1500;
        final double latitudinalAdjustment = Math.cos(Math.PI * latLngPoint.latitude / 180.0);
        final double arg = EQUATOR_LENGTH * mapWidth * latitudinalAdjustment / (desiredMeters * 256.0);
        double valToZoom = Math.log(arg) / Math.log(2.0);

        googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLngPoint, Float.valueOf(String.valueOf(valToZoom))), TIME_ANIMATION_MILIS , null);
    

附言使用@sho 回答和@Lionel Briand 评论

【讨论】:

这是最好的公式 公式中的 256 是什么?有没有这个的文档【参考方案8】:

我确信有很多方法可以找到它我使用这种技术来计算缩放级别

 mMap.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() 
        private float currentZoom = -1;
        @Override
        public void onCameraChange(CameraPosition position) 
            if (position.zoom != currentZoom)
                currentZoom = position.zoom;  // here you get zoom level
                Toast.makeText(this, "Zoom Value is : "+currentZoom, Toast.LENGTH_SHORT).show();
            
        
    );

【讨论】:

以上是关于Android:如何将地图视图的缩放级别设置为当前位置周围 1 公里半径?的主要内容,如果未能解决你的问题,请参考以下文章

如何设置地图视图的缩放级别,使三个引脚可见?

如何设置谷歌地图缩放级别以显示所有标记?

Google Maps API v3:如何将缩放级别和地图中心设置为用户提交的位置?

android开发 高德地图怎么设置显示的范围,并且设置缩放的最大级别

如何在谷歌地图中设置缩放级别

Android Google Maps v2 - 为 myLocation 设置缩放级别