7/21/2009
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
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