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

发布于 2024-11-07 01:58:29 字数 821 浏览 1 评论 0原文

我想将地图视图设置为缩放至 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 公里半径的地图。有人可以告诉我我在这里做错了什么吗?

I want to set the map view zoomed to 1km radius but cant figure out how?

The doc says that the zoom level 1 will map earths equator to 256 pixels. So how do I calculate which zoom level I need to set so that the map view shows area in 1KM radius?

UPDATE:
After reading a few blog posts I wrote the following code:

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

The idea is that first I calculate Meters per pixel in the zoom level 1, which according to google shows equator of earth using 256 pixels. Now every subsequent zoom level magnifies by a level of 2 so I half the meters per pixel for every zoom level. I do this until I have a zoom level where meters per pixel multiplied by the screen width gives me less than 2000 i.e 2 Km across.

But I dont think that the zoom level I am getting is showing the map of 2Km radius. Can some one tell me what I am doing wrong here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

缱倦旧时光 2024-11-14 01:58:29

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

1) 在对象上以所需半径画一个圆

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

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

although this answer is logical and i find it working but the results are not accurate i dont know why but i tired this approach and this technique is far more accurate.

1) Make a circle on object with desired radius

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

2) Pass that object to this function and set the zoom level
Here's 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;
}
安静 2024-11-14 01:58:29

下面的代码是最终使用的。给定屏幕宽度,并且在缩放级别 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;
}

The following code is what ended up using. Given the screen width and the fact that at zoom level 1 the equator of Earth is 256 pixels long and every subsequent zoom level doubles the number of pixels needed to represent earths equator, the following function returns the zoom level where the screen will show an area of 2Km width.

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;
}
静谧 2024-11-14 01:58:29

我最终使用了以下实用程序:

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

I ended up using the utils from:

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

I extracted the class from the lib, so you don't need the whole library.
Instead of setting zoom level, you use bounds. The result is the same.

Code to show exactly 1 kilometer:

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

The class extracted (slightly changed) from the lib:

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;
    }
}
丶情人眼里出诗心の 2024-11-14 01:58:29

最终工作解决方案:

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

ps 使用 @sho 答案和 @Lionel Briand 评论

FINAL working solution:

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

p.s. Using @sho answer and @Lionel Briand comment

断念 2024-11-14 01:58:29

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

Google maps seems to work closely to miles/pixel. At zoom=13, 1 mile= 100 pixels. 2 miles = 200 pixels. Each zoom leven increases or decreases by a factor of 2. Therefore, at Zoom 14, 1 mile = 200 pixels and at zoom 12, 1 mile = 50 pixels.

与风相奔跑 2024-11-14 01:58:29

使用循环来计算缩放级别是非常幼稚的。
使用数学会更好。

这里是函数(返回类型: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 公式)。

或者

如果您没有这样的库(或者您不搜索库,或者您只想要完整的代码有效)在这个答案的底部,您可以找到整个工作代码,其中包含使用表格(使用 Vincenty Formulae 计算)的 double calcParallelLegth(double atLatitude) 的实现所有纬度的平行长度,公差为 3%。


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

下面的公式解释:

用简单的方式表达!

让我们将问题分为两部分。

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

第 2 部分
针对不同尺寸的图像调整缩放

解决第 1 部分

图像尺寸为 256x256。
缩放级别 0 显示整个赤道。
随后的每个缩放级别都让我看到之前的一半。

赤道长 40,075,016 米(根据 WGS-84 (*1)
Vincenty 公式 (*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 是所需的 Zoom_level。
visible_distance 是图像水平显示的米数。

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

让我们找出缩放公式。
这就是数学发挥其魔力的地方(“无聊”的魔力)。

   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)

......
宽度->宽度/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

所以,这是从Let's do it

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 图像中可见 1km,那么

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

Using loop to calculate zoom level is very naive.
It is way long better to use math.

Here the function (return type: 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;
}

example call:

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

The math formulae to calculate the zoom level is:

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.

That's all folks! :-)

ATTENTION: The solution above as the accepted answer only works for zoom levels next to the equator.
If you want a solution that works with all latitudes you need the length of the parallel at the same latitude of the zoom level you want to compute. The calcZoom method changes to

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

Where this.calcParallelLegth(atLatitude) returs the length of the parallel at the atLatitude latitude.

You can compute the length yourself with some library (preferably using Vincenty formulae).

Alternatively

If you don't have such a library (or you don't search for a library, or you just want a complete code that works) at the bottom of this answer you can find the whole working code with an implementation of double calcParallelLegth(double atLatitude) that uses a table (computed using Vincenty Formulae) with parallel length at all latitudes with 3% tollerance.


NOTE:
YOU NEED TO READ BELOW ONLY IF YOU AND TO UNDERSTAND THE FORMULA (OR CHECK IF THE FORMULA IS RIGHT)

Formulae explanation below :

Putting it in a simple way!

Let's split the problem in two parts.

Part 1
calc the zoom for an 256x256 size image

Part 2
adapt the zoom for an image with a different size

Resolving Part 1

Image size is 256x256.
Zoom level 0 shows the whole equator.
each subsequent zoom level let me see half then before.

Equator is 40,075,016 meters long (according WGS-84 (*1) and
Vincenty formulae (*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

As you can see above, each subsequent zoom level let me see half then before.

zoom is the zoom_level wanted.
visible_distance is how many meters the image shows horizontally.

if you want 1km than you have to calculate zoom with visible_distance=1000

Let's find out the zoom formulae.
Here is where math do it's magic ("boring" magic stuff).

   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)

the zoom level formulae for an 256x256 image is:

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

Part 1 DONE!!

Resolving Part 2

Adapt the zoom to the wanted image size.

Every time that the image width doubles, the zoom needed to see the whole equator increases of one.

Example:
In an image 512x512 the zoom needed to see the whole equator is 1.
In an image 1024x1024 the zoom needed to see the whole equator is 2.
In an image 2048x2048 the zoom needed to see the whole equator is 3.

That said

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 (needed to see the whole equator)

this means that (zoom_level is

- 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

Whe need x to adapt the zoom the the wanted image size.

So, it is a matter of extract x from

width/256 = 2^x

Let's do it

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)

Now we have the x formula.

the zoom level formulae for an WIDTHxHEIGHT image is:

zoom = zoom256 + x

So, if you want 1km visible in an 512x512 image than

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

If it must be integer

zoom = floor(zoom) = 16

DONE!

(*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)

Here is the complete code that computes the zoom level at every latitude ed image width.

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;
    }
}
凶凌 2024-11-14 01:58:29

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

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;

希望这对某人有帮助。

I've converted the accepted answer to return a double value, since the Android Google Maps library uses floating point zoom levels, and also account for latitudes away from the equator.

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

As an aside, for best results on Android don't pass the view's real pixel count, but the dimension scaled for the device's pixel density.

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

Hope this helps someone.

小忆控 2024-11-14 01:58:29

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

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

I am sure there are many methods to find it I use this technique to calculate zoom level

 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();
            }
        }
    });
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文