Determining Best Map View for an array of locations

 

In the past it has been useful to be able to determine the best map view to display an array of locations. In the AJAX control there is a method call SetMapView which determines the best map view for an array of coordinates. The VEWS and Silverlight control do not have this functionality. A long time ago someone wanted to know how to determine the best map view before loading the map. This required us to calculate out the best map view ourselves using map scale information. I later implemented the same method when working with the VEWS so that I could geo-reference images from the Imagery service (http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!488.entry). The method worked fairly well but I knew there was a better way to do this. When working with the Silverlight control I came across a situation where I needed this functionality again so I decided to create the updated method for determining the best map view using similar math that is used for the tiling system. This eliminated the need to maintain a list of scales for each zoom level and also reduce the amount of calculations that needed to be performed. Below is the updated algorithm for determining the best map view for a list of locations:

/// <summary>
/// Calculates the best map view for a list of locations for a map
/// </summary>
/// <param name="locations">List of location objects</param>
/// <param name="mapWidth">Map width in pixels</param>
/// <param name="mapHeight">Map height in pixels</param>
/// <param name="buffer">Width in pixels to use to create a buffer around the map. This is to keep pushpins from being cut off on the edge</param>
/// <returns>Returns a MapViewSpecification with the best map center point and zoom level for the given set of locations</returns>
public static MapViewSpecification BestMapView(IList<Location> locations, double mapWidth, double mapHeight, int buffer)
{
    MapViewSpecification mapView;
    Location center = new Location();
    double zoomLevel = 0;

    double maxLat = -85;
    double minLat = 85;
    double maxLon = -180;
    double minLon = 180;

    //calculate bounding rectangle
    for (int i = 0; i < locations.Count; i++)
    {
        if (locations[i].Latitude > maxLat)
        {
            maxLat = locations[i].Latitude;
        }

        if (locations[i].Latitude < minLat)
        {
            minLat = locations[i].Latitude;
        }

        if (locations[i].Longitude > maxLon)
        {
            maxLon = locations[i].Longitude;
        }

        if (locations[i].Longitude < minLon)
        {
            minLon = locations[i].Longitude;
        }
    }

    center.Latitude = (maxLat + minLat) / 2;
    center.Longitude = (maxLon + minLon) / 2;

    double zoom1=0, zoom2=0;

    //Determine the best zoom level based on the map scale and bounding coordinate information
    if (maxLon != minLon && maxLat != minLat)
    {
        //best zoom level based on map width
        zoom1 = Math.Log(360.0 / 256.0 * (mapWidth – 2*buffer) / (maxLon – minLon)) / Math.Log(2);
        //best zoom level based on map height
        zoom2 = Math.Log(180.0 / 256.0 * (mapHeight – 2*buffer) / (maxLat – minLat)) / Math.Log(2);
    }

    //use the most zoomed out of the two zoom levels
    zoomLevel = (zoom1 < zoom2) ? zoom1 : zoom2;

    mapView = new MapViewSpecification(center, zoomLevel);

    return mapView;
}

Advertisements

15 thoughts on “Determining Best Map View for an array of locations

  1. HI There: First this algorithim is working beautifully for many shapes (Country polygon) drawn, However when Im plotting only the United States The map is panned to the side (the US is not in the center)… Why is that?

  2. When the above logic is used to calculate for pushpins, I get a zoom value of 11 and one of the pushpin is not appearing in the image. But when the zoom is 10 all of them appears.
    check the link below. Am I doing something wrong?
    http://dev.virtualearth.net/REST/v1/Imagery/Map/road/33.0997,-96.6821/10?pushpin=33.0997,-96.6821;1;1&pushpin=33.121754,-96.662309;1;2&pushpin=33.1286,-96.7298;1;3&pushpin=33.0376,-96.7077;1;4&pushpin=33.1048,-96.8123;1;5&v=1&mapsize=350,400&o=xml&key=bingKey

    • You need to provide a buffer value into the calculation. The buffer value should be the half the width of the pushpin. This ensures that the pushpin shows up. Based on what I see I would recommend a buffer of 20 pixels.

      • You have to approximate this as there is no way to determine this unless you are using a custom pushpin and another workaround I created. As I said before use a buffer of 20 pixel which is approximately the width of the pushpin in the example provided.

  3. Dude, thanks a lot for this code, I was looking for a way to calculate the zoom for some bounds and you just saved my live, I ported this to JavaScript and works awesome!

  4. Hi,

    I found this as a handy article.

    But, I did struggle to understand the zoom formula:

    Math.Log(180.0 / 256.0 * (mapHeight – 2*buffer) / (maxLat – minLat)) / Math.Log(2);

    It would be great if you can describe each constants being used in the formula and how did you derive formula.

    Thanks in advance for the help.

    • The math behind this is actually pretty straight forward. This calculation determines the best zoom level based on the height of the map. In the code sample I also do the same based on the width of the map and then take the most zoomed out value of the two. The Log(x)/Log(2) where x is the rest of the math is the opposite of 2^x. Basically the with and height of the map in pixels at any zoom level is equal to 256 pixels * 2^zoom. The buffer simply accounts for a pixel sized buffer around the map and is used to account for the size of pushpin icons as the calculation itself without the buffer only accounts for the single pixels of the coordinates and does not account for the pushpin icon size. A lot of the math is based on this article: http://msdn.microsoft.com/en-us/library/bb259689.aspx

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s