Saturday, July 20, 2013

Calculate Google Map zoom level on Android

On Android, Google Map API doesn't have function to export the map to image file directly. But the Google provides Static Maps API to render map into image via http request. An issue comes up when we need to show all locations on the map, so we have to supply the zoom level parameter.

If the map is rendering on the GoogleMap, it's easy to get the center and zoom of the boundary with the code:

 final LatLngBounds.Builder builder = new LatLngBounds.Builder();  
 for (LatLng latlng : list) {  
   builder.include(latlng);   
 }  
 mMap.setOnCameraChangeListener(new OnCameraChangeListener() {  
   @Override  
   public void onCameraChange(CameraPosition arg0) {  
     // Move camera.  
     mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 10));  
     // Remove listener to prevent position reset on camera move.  
     mMap.setOnCameraChangeListener(null);  
     LatLng center = mMap.getCameraPosition().target;  
     float zoom = mMap.getCameraPosition().zoom;  
   }  
 });  

But when we need to export by batch or from the server side, we won't load each map data onto the map view to execute above code. So I find a way to calculate them by formula.

The center point is simple to do:

 final LatLngBounds bounds = builder.build();  
 LatLng ne = bounds.northeast;  
 LatLng sw = bounds.southwest;  
 LatLng center = new LatLng((ne.latitude + sw.latitude)/2,  
             (ne.longitude + sw.longitude)/2);  

There are several ways to calculate the zoom level, but I find this code in Javascript by John S is more accurate (I convert them into java code)

 public static int getBoundsZoomLevel(LatLng northeast,LatLng southwest,   
                   int width, int height) {  
   final int GLOBE_WIDTH = 256; // a constant in Google's map projection  
   final int ZOOM_MAX = 21;  
   double latFraction = (latRad(northeast.latitude) - latRad(southwest.latitude)) / Math.PI;  
   double lngDiff = northeast.longitude - southwest.longitude;  
   double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;  
   double latZoom = zoom(height, GLOBE_WIDTH, latFraction);  
   double lngZoom = zoom(width, GLOBE_WIDTH, lngFraction);  
   double zoom = Math.min(Math.min(latZoom, lngZoom),ZOOM_MAX);  
   return (int)(zoom);  
 }  
 private static double latRad(double lat) {  
   double sin = Math.sin(lat * Math.PI / 180);  
   double radX2 = Math.log((1 + sin) / (1 - sin)) / 2;  
   return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;  
 }  
 private static double zoom(double mapPx, double worldPx, double fraction) {  
   final double LN2 = .693147180559945309417;  
   return (Math.log(mapPx / worldPx / fraction) / LN2);  
 }  




5 comments:

  1. Thank you for the zoom level method, works like a charm :).

    ReplyDelete
  2. Many times it give zoom level 0.

    ReplyDelete
  3. can u please provide example

    ReplyDelete
    Replies
    1. Hi,

      This is 3 years old post! I haven't worked with Android app anymore. Not sure above code is still working.

      Anyway, basically once you have a bounds (locations/points collection) then you can get
      LatLng ne = bounds.northeast;
      LatLng sw = bounds.southwest;

      then pass them into the getBoundsZoomLevel(LatLng northeast,LatLng southwest, int width, int height)

      width, height are dimension of the image you'll want to generate
      once we get the zoom level, pass it to Google static maps API

      See examples here:
      https://developers.google.com/maps/documentation/static-maps/styling#examples


      Delete