16

We want to display several markers on a static map and want to calculate the optimal zoom-level like Google Maps does. We already calculated the bounding rectangle and the centerpoint of the map but now we have a hard time to calculate the correct zoomlevel to display the whole bounding rectangle. Can someone please point us into the right direction?

4
  • It's not very sophisticated, but why not just multiply the MBR by 120% and zoom to that? Anything else depends on your definition of "optimal", doesn't it?
    – ThomM
    Commented Feb 2, 2012 at 0:06
  • +1 to ThomM's idea. This is exactly what ArcGIS does. Commented Feb 2, 2012 at 0:17
  • 2
    Sorry, but what is this MBR?
    – Rodrigo
    Commented Feb 11, 2015 at 23:52
  • 1
    MBR = minimum bounding rectangle, better known as the bounding box, i.e. the smallest rectangular area that spatially contains the collection of some thing. Commented Aug 15, 2021 at 22:17

3 Answers 3

9

To get the zoom level, you'll need to know the pixel dimensions of your map. You'll also need to do your math in spherical mercator coordinates.

  1. Convert latitude, longitude to spherical mercator x, y.
  2. Get distance between your two points in spherical mercator.
  3. The equator is about 40m meters long projected and tiles are 256 pixels wide, so the pixel length of that map at a given zoom level is about 256 * distance/40000000 * 2^zoom. Try zoom=0, zoom=1, zoom=2 until the distance is too long for the pixel dimensions of your map.
0
7

This is the C# code I use in Maperitive:

    public void ZoomToArea (Bounds2 mapArea, float paddingFactor)
    {
        double ry1 = Math.Log((Math.Sin(GeometryUtils.Deg2Rad(mapArea.MinY)) + 1) 
            / Math.Cos(GeometryUtils.Deg2Rad(mapArea.MinY)));
        double ry2 = Math.Log((Math.Sin(GeometryUtils.Deg2Rad(mapArea.MaxY)) + 1) 
            / Math.Cos(GeometryUtils.Deg2Rad(mapArea.MaxY)));
        double ryc = (ry1 + ry2) / 2;
        double centerY = GeometryUtils.Rad2Deg(Math.Atan(Math.Sinh(ryc)));

        double resolutionHorizontal = mapArea.DeltaX / Viewport.Width;

        double vy0 = Math.Log(Math.Tan(Math.PI*(0.25 + centerY/360)));
        double vy1 = Math.Log(Math.Tan(Math.PI*(0.25 + mapArea.MaxY/360)));
        double viewHeightHalf = Viewport.Height/2.0f;
        double zoomFactorPowered = viewHeightHalf
            / (40.7436654315252*(vy1 - vy0));
        double resolutionVertical = 360.0 / (zoomFactorPowered * 256);

        double resolution = Math.Max(resolutionHorizontal, resolutionVertical) 
            * paddingFactor;
        double zoom = Math.Log(360 / (resolution * 256), 2);
        double lon = mapArea.Center.X;
        double lat = centerY;

        CenterMapOnPoint(new PointD2(lon, lat), zoom);
    }
  • mapArea: bounding box in long/lat coords (x=long, y=lat)
  • paddingFactor: this can be used to get the "120%" effect ThomM refers to. Value of 1.2 would get you the 120%.

Note that in my case zoom can be a real number. In the case of Web maps, you need a integer zoom value, so you should use something like (int)Math.Floor(zoom) to get it.

Of course, this code only applies to Web Mercator projection.

6
  • 1
    Thanks for this. Is the code for CenterMapOnPoint available? Commented May 6, 2014 at 14:54
  • Perfect! I've used this to calculate zoom of BoundingBox in OsmDroid SDK and it works :)
    – Billda
    Commented Feb 9, 2018 at 15:35
  • Where do I find these libraries? I can't find a GeometryUtils or Bounds2 assembies. Do I need to download something from maperitive? I only see an app there.
    – Dowlers
    Commented Jul 3, 2019 at 17:55
  • @Dowlers GeometryUtils is just used here to convert degrees to radians and back, it's a simple math formula. Bounds2 is basically just a rectangle structure. This code was provided more as a pseudocode than something directly copy-pastable.
    – Igor Brejc
    Commented Jul 12, 2019 at 7:37
  • 1
    This is awesome @IgorBrejc- thank you so much! I converted it to python here in case others are writing python code: gist.github.com/mappingvermont/d534539fa3ebe4a1e242644e528bf7b9 Commented Apr 21, 2020 at 14:50
1

If you are using OpenLayers then Map.getZoomForExtent will calculate the highest zoom level that can fit the entire extent on the map. The extent needs to be in the map's projection. You can also use a fill_factor to avoid displaying points on the edge of the map, and max_zoom to limit the possible zoom:

extent = extent.scale(1/fill_ratio);
var zoom = Math.min(map.getZoomForExtent(map_extent), max_zoom);
map.setCenter(extent.getCenterLonLat(), zoom);
1
  • this didn't fix my issue, but it lead me to find Leaflet's fitBounds method, which saved my day.
    – SamuelDev
    Commented Feb 5, 2020 at 11:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.