Ricky's profileRicky's Bing Maps BlogBlogSkyDrive Tools Help

Blog


    7/21/2009

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


    7/2/2009

    Load 3D map control at a specific location

    In the latest release of the 3D map control some new properties were added that allow you to specify where the map should appear when loading. The benefit of this is that the map will load at a specific location where as before you had to load the map and see the whole globe then call the FlyTo method to fly to your location. The properties are called StartAltitude, StartPitch, StartHeading, StartLongitude, and StartLatitude. To set these properties you should use an event handler on the RenderEngine.Initialized event. For example:

    this.globeControl.Host.RenderEngine.Initialized += new EventHandler(Initialized);

    Inside the method that gets called by the Initialized event the data sources, and the initial location information can be loaded:

    private void Initialized(object sender, EventArgs e)
           {
               // at this point, the control is fully initialized and we can interact with it without worry.

               // set various data sources, here for elevation data, terrain data, and model data.
               this.globeControl.Host.DataSources.Add(new DataSourceLayerData("Elevation", "Elevation", @"http://go.microsoft.com/fwlink/?LinkID=98774", DataSourceUsage.ElevationMap));
               this.globeControl.Host.DataSources.Add(new DataSourceLayerData("Texture", "Texture", @"http://go.microsoft.com/fwlink/?LinkID=98772", DataSourceUsage.TextureMap));
               this.globeControl.Host.DataSources.Add(new DataSourceLayerData("Models", "Models", @"http://go.microsoft.com/fwlink/?LinkID=98775", DataSourceUsage.Model));

               //Set the intial globe position
               this.globeControl.StartAltitude = 0;
               this.globeControl.StartPitch = -14;
               this.globeControl.StartHeading = 89.42;
               this.globeControl.StartLongitude = -115.12571;
               this.globeControl.StartLatitude = 36.07639;

               // Using this event is the proper way to handle loading and activation.
               this.globeControl.Host.CommunicationManager.AttachToEvent(EngineEvents.Group, EngineEvents.OnPlugInLoaded, "Loaded", PlugInLoaded);

               // Plug-ins can also be loaded by path to a dll, but this one is built-in se we reference by type.
               // If loading by path, it is possible to use both filesystem and http paths.
               // Also, if doing that it may be appropriate to execute the LoadPlugIn call on a worker thread,
               // and handle the result in OnPlugInLoaded.
               Guid g = this.loader.LoadPlugIn(typeof(NavigationPlugIn));           
           }

     

    WinForm application that demonstrates how to do this has been uploaded here: http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/VE3DFlyToExample%7C_WinForm.zip