7

I am trying to center a Google Map to the user location while giving a reasonable zoom level taking into account the accuracy of that location. Could anybody describe how should I compute it? Which variables are involved, how do you achieve this?

Didac Perez Parera
  • 3,734
  • 3
  • 52
  • 87
  • 1
    [This post](http://stackoverflow.com/questions/2859705/google-maps-api-geocoding-accuracy-chart) should help with the first part of your question on finding location accuracy. Once you know approximately how much accuracy you have, then figuring out what zoom level to use will be simple (and really depends a bit on the application). Also, since V2 of that API is already deprecated, [this](http://stackoverflow.com/questions/3015370/how-to-get-the-equivalent-of-the-accuracy-in-google-map-geocoder-v3) shows how to work it in with the newest API. – Question Marks Aug 22 '13 at 17:41
  • I'd like to know, too. I even posted this on math.SE: https://math.stackexchange.com/questions/887868/how-can-i-find-a-hyperbolic-function-denoting-zoom-levels – Ky - Aug 05 '14 at 07:40

3 Answers3

12

What you are looking for is the formula that calculates the zoom level based on the accuracy of the location.

I managed to come up with this formula which (in my tests) worked pretty well.

Formula

This can be simplified (might not seem so) to this:

Simplified/Scarified formula

This scary looking thing is what you want.

EquatorLength is 40,075,004 meters. While the Meters/Pixel can be calculated by diving the diameter of the accuracy circle by the length of the device screen (in pixels).

Here's a sample program that I used to test this formula:

GoogleMap mMap;

@Override
protected void onStart() {
    super.onStart();

    mMap = ((MapFragment)getFragmentManager().findFragmentById(R.id.map)).getMap();

    // Enable user's location layer
    mMap.setMyLocationEnabled(true);

    mMap.setOnMyLocationChangeListener(new GoogleMap.OnMyLocationChangeListener() {
        @Override
        public void onMyLocationChange(Location location) {
            // Location lat-lng
            LatLng loc = new LatLng(location.getLatitude(), location.getLongitude());

            // Location accuracy diameter (in meters)
            float accuracy = location.getAccuracy() * 2;

            // Screen measurements
            DisplayMetrics metrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(metrics);
            // Use min(width, height) (to properly fit the screen
            int screenSize = Math.min(metrics.widthPixels, metrics.heightPixels);

            // Equators length
            long equator = 40075004;

            // The meters per pixel required to show the whole area the user might be located in
            double requiredMpp = accuracy/screenSize;

            // Calculate the zoom level
            double zoomLevel = ((Math.log(equator / (256 * requiredMpp))) / Math.log(2)) + 1;

            Log.e(TAG, String.format("Accuracy: %f. Screen Width: %d, Height: %d",
                    accuracy, metrics.widthPixels, metrics.heightPixels));
            Log.e(TAG, String.format("Required M/Px: %f Zoom Level: %f Approx Zoom Level: %d",
                    requiredMpp, zoomLevel, calculateZoomLevel(screenSize, accuracy)));

            // Center to user's position
            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(loc, (float) zoomLevel));

            // Prevent the camera centering on the user again
            mMap.setOnMyLocationChangeListener(null);
        }
    });

}

private int calculateZoomLevel(int screenWidth, float accuracy) {
    double equatorLength = 40075004; // in meters
    double metersPerPixel = equatorLength / 256;
    int zoomLevel = 1;
    while ((metersPerPixel * (double) screenWidth) > accuracy) {
        metersPerPixel /= 2;
        zoomLevel++;
    }

    return zoomLevel;
}

Few things to note:

  • This answer is based on this and implements it to check the values generated
  • Accuracy is the radius of user's location and according to the docs it can be up to 68% correct.

Any corrections are very welcome.

Community
  • 1
  • 1
Simas
  • 43,548
  • 10
  • 88
  • 116
  • why do you do `getWindowManager().getDefaultDisplay().getMetrics(metrics);`? – Ky - Aug 05 '14 at 16:45
  • @Supuhstar to get the device screen measurements and store them inside variable `metrics`. – Simas Aug 05 '14 at 16:48
  • Why don't you use your `calculateZoomLevel` method in the `OnMyLocationChangeListener`? – Ky - Aug 05 '14 at 16:49
  • 1
    @Supuhstar There's a link in my answer where it points to the answer with `calculateZoomLevel`. However that function calculates approximate zoomLevel, which can be clearly seen from the log messages this app spits out. – Simas Aug 05 '14 at 16:53
  • Alright... why is `equator` a `long` instead of an `int`? Sorry, just trying to understand your choices, here. ^^; – Ky - Aug 05 '14 at 17:03
  • @Supuhstar hmm that's a typo :-). However it doesn't affect the workflow. – Simas Aug 05 '14 at 17:06
  • ah, alright. I was thinking you might've been doing some calculations that go over `2^31-1` – Ky - Aug 05 '14 at 17:11
  • Great answer. All i changed was this, and i get a better functionality double zoomLevel = ((Math.log(equator / (256 * accuracy))) / Math.log(2)) + 1; – sdelvalle57 Aug 04 '16 at 21:47
  • This seems to over zoom for map views with smaller heights. IE if I have a mapView that is only 200dp in height. Is it possible to constrain the zoom based on the height of the mapView such that the accuracy circle fits within the map? – lostintranslation May 04 '20 at 15:49
1

If you're looking for something simple:

var zoom = Math.min(20, Math.max(1, Math.log2(591657550/accuracy)-2));

Tweak -2 to get the desired zoom.

Checkout this answer for a chart corresponding zoom with accuracy.

bendytree
  • 13,095
  • 11
  • 75
  • 91
0

Thanks @Simas! I picked up your algo to make this extension to GMSMapView to calculate for the ideal zoomLevel given the accuracy of a CLLocation.

I had to make adjustments to consider devices with Retina displays since each pixel is not exactly the same as 1 point on the screen:

extension GMSMapView {

    func getIdealZoomLevel(usingLocation location:CLLocation)->Float {

        let retinaScale = Double(UIScreen.mainScreen().scale)

        let equatorLength : Double = 40075004 // in meters
        var metersPerPixel = equatorLength / 256
        let accuracy = location.horizontalAccuracy
        // I used height because I'm on landscape, but moving forward I'll have to get the Min of the width and height.
        // I also took only 75% of the height to give it some margin
        let screenWidth : Double = Double( self.frame.size.height) * 0.75

        var display = metersPerPixel * (screenWidth / retinaScale)

        var zoomLevel : Float = 0.0

        while (display > accuracy) {
            metersPerPixel /= 2
            display = metersPerPixel * (screenWidth / retinaScale)
            zoomLevel += 1
        }

        return zoomLevel
    }
}

It's for a Swift project I'm working on and right now, I'm able to display a good proximity enclosing the radius within the given CLLocation.

Hope this helps.

Rio Bautista
  • 433
  • 5
  • 4