11/11/2009
There are two Bing events coming up that you should attend if you are in the area.
Developing with Bing Maps & Silverlight
Location: Microsoft
Reading, UK
Date: November 25th, 2009 10am – 4pm GMT
The Bing Maps Platform brings location data to life by making it easier to visualize, understand and analyze. It’s already being used by thousands of governments, companies and organizations worldwide.
This developer focused event will show you how to build well performing and visually engaging web mapping applications using the Bing Maps Silverlight control.
The event will begin with an update, bringing you up to speed with the latest from the Bing Maps platform. The rest of the agenda will cover some of the more standard mapping requirements such as database connections, before diving into the more sophisticated spatial analysis and performance optimization.
The Bing Maps team will be joined by partners Earthware and Infusion Development. Earthware will showcase their development of the Bing Maps World Tour using the Silverlight Control and Windows Azure, whilst Infusion will demonstrate how to create dynamic tile layers in order to visualize large amounts of frequently changing data.
You can register here: http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032428360&Culture=en-GB
Bing Maps UK User Group – First Meeting
Location: Microsoft London
100 Victoria Street, London, UK
Date: January 13th, 2010 6pm - 9pm
Our first meeting of the new Bing Maps UK User Group. Open to all newbie's, experienced and Bing maps wizards there should be something for everyone.
We will start informally around 6pm with coffee and socializing and start officially at 7pm finishing up around 9pm and off to the pub. The exact program of sessions etc will be published once we have everything confirmed. If you would like to host a session yourself at this event, or future ones please let us know in the comments below.
The event will focus on developers but any of you marketing and sales types are more than welcome.
Beer and pizza is being kindly donated by Microsoft to let us all relax and unwind.
More information here: http://bingmapsuk.ning.com/events/inaugural-uk-bing-maps-ug
11/10/2009
With the official release of the Bing Maps Silverlight control there has been some changes from the CTP version. This article will address the required changes needed to migrate from the CTP version to the production version.
The first change is that the control now requires Silverlight 3. So if you are still using Silverlight 2 you will have to upgrade. You can get the required files to upgrade to Silverlight 3 here: http://silverlight.net/getstarted/
With the production version being released you are now required to authenticate the map. In the CTP version Microsoft took care of the authentication. The Silverlight control does not use tokens like the AJAX control; you can now use an application key. This removes the need to retrieve a client token when you load your map which means faster loading times. To get a key you first need an account. You can get an account here: https://www.bingmapsportal.com. After creating an account you can sign in using your Windows Live ID. Once signed in you can create up to 5 keys by clicking on the Create or view keys on the left side panel. To create a new key, enter the name of your application and the URL which corresponds to the website where the key will be used. Then click the Create key button. Once the key is created you can copy it and use it in your code. There are two ways to add this application id to your code. The first is to set the ApplicationId property of an ApplicationIdCredentialsProvider object and then use this object to set the CredentialsProvider property of the MapBase object. Alternatively you can add the application key to the XAML like so:
| <UserControl x:Class="SilverlightApplication1.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl" Width="1024" Height="768"> <Grid x:Name="LayoutRoot" Background="White"> <m:Map x:Name="myMap" CredentialsProvider="Your key" Mode="Road"/> </Grid> </UserControl> |
You will need to update your Virtual Earth map control assembly reference. The new assemblies have been renamed as well. You will need to replace Microsoft.VirtualEarth.MapControl.dll with both Microsoft.Maps.MapControl.dll and Microsoft.Maps.MapControl.Common.dll.
With the change in assemblies the namespaces have also changed. Here is a mapping of the new namespaces:
| Old Namespace | New Namespace |
| Microsoft.VirtualEarth.MapControl | Microsoft.Maps.MapControl |
| Microsoft.VirtualEarth.MapControl.Core | Microsoft.Maps.MapControl.Core |
| Microsoft.VirtualEarth.MapControl.Design | Microsoft.Maps.MapControl.Design |
| Microsoft.VirtualEarth.MapControl.Navigation | Microsoft.Maps.MapControl.Navigation |
| Microsoft.VirtualEarth.MapControl.Overlays | Microsoft.Maps.MapControl.Overlays |
The following classes and properties have been removed or modified:
| Old class | New class |
| AerialWithLabelsMode class | Removed. Use the Labels property of the AerialMode class to display labels. |
| INavigationBarCommand interface | Removed |
| MapBase class | Many members have changed. Use the Children property to add and remove child elements. |
| MapLayer class | Many members have changed. This class now inherits from MapLayerBase. Use the Children property to add and remove child elements. |
| MapMode class | Many members have changed. |
| MapViewSpecification class
| Removed. Use the SetView method to set the map view. You can also use the map view properties Center, ZoomLevel, Heading, and Pitch. |
| MapViewSpecificationConverter class | Removed. |
| PositionMethod enumeration | Removed. Use the PositionOrigin struct instead. |
10/4/2009
Currently when storing data in the customer service site you have the ability to query your point location data by entity id, property, radius search, and find near route. There are two key search functionalities missing. The first is bounding box searching, and the second is point in polygon searches. A bounding box is simple 4 sided regular polygon and as such if we can perform a point in polygon search we can use the same algorithm to perform a bounding box search. This post is going to outline two methods that can be used to perform such a search against data that is stored in the Customer Service Site.
Method 1
Lets start off with a polygon (blue). If we find the maximum and minimum latitude and longitude coordinates that this polygon has we can create a bounding box (green) that encloses the polygon. We can then calculate the center point and radius from center point to a corner of this bounding box. Once we know this information we can then enclose this bounding box with a circle (red). The information used to create this circle can be used with the radius search tools that are currently available.

If you perform a radius search using the calculated information you will end up with a lot of extra data points. You can filter this data points by running them through a point in polygon algorithm like the one outlined here: http://msdn.microsoft.com/en-us/library/cc451895.aspx. You can then return the filtered data to the user. Using this method your polygon must fit inside a bounding box whose sides are no longer than 353.55 miles. The benefit of this approach is that there are only a few calculations required initially. One down side is that there is a lot of extra data that could be potentially returned.
Method 2
The second method is very similar to the first. The maximum and minimum latitude and longitude coordinates of the polygon are used to create a bounding box so that the center of the polygon can be calculated. However, instead of calculating the radius from the center point to a corner of the bounding box, we can loop through and calculate the radii’s from the center point to each point in the polygon. The largest radius can then be used to perform a radius search.
Using this approach should reduce the amount of data that is returned by radius search as it is able to enclose the polygon tighter than that previous method. The down side is the number of calculations that need to be performed initially. For complex polygons this can result in slower performance. Using this method you are limited to a maximum radius of 250 miles.
Conclusion
Using either of these methods will allow you to perform both bounding box and point in polygon searches against your data in the customer service site. Which method should you use. If you are performing bounding box searches or are using complex, predefined polygons then the first method would be ideal. If youare letting the user create the polygon chances are there will only be a limited number of points and as such method 2 would be ideal.
I have put together an example that allows you to draw out polygons using the right mouse button (press the left mouse button when done). After you have drawn a polygon you can then use either method to query the FourthCoffeeShops data source. You can down load the sample code here: http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/CSSFindByPolygon.zip
10/3/2009
When I first started with Virtual Earth a few years ago I always wanted to know how to extract the altitude of buildings from the 3D control but unfortunately there wasn’t any documentation on how to create custom plug-ins. There has been numerous people on the forums over the past couple of years who have also been asking for this. I had a good idea of how to go about this but just never got around to doing it, so here it goes. Lets start off with a screen shot of the finished product:
Assuming you already have the 3D control installed the first step is to create a class library project in Visual Studios and call it “VE3DMouseAltitude”. The next step is to reference the dll’s that will be needed. You will need to add references to the following dll’s:
Microsoft.MapPoint.Geometry
Microsoft.MapPoint.Rendering3D
Microsoft.MapPoint.Rendering3D.Utility
Microsoft.MapPoint.UtilityPartialTrust
You then need to add a class file to your project called “VE3DMouseAltitudePlugin.cs” and a HTML page called “MouseClickAltitudeFinder.html”. You can now add a strong named key to your project. To create a strongly name key for this plug-in:
- Open a Visual Studio Command Prompt.
- At the command prompt, type cd C:\Samples\Test (or the directory where you created your application) and press ENTER.
- At the command prompt, type sn -k MouseClickAltitudePlugin.snk and press ENTER.
- At the command prompt, type exit, and press ENTER.
- In Visual Studio, right click your project and select Properties.
- Click the Signing tab and check Sign the assembly.
- In the Choose a strong name key file drop down list, select <Browse…>
- Select the MouseClickAltitudePlugin.snk file and click Open. The strong name file is automatically added to your project.
- Save the properties window and then close it.
Your solution explorer should now look like this:
In the VE3DMouseAltitudePlugin.cs file we can add the following references to the top of the file:
|
using System; using System.Collections.Generic; using System.Runtime.InteropServices;
using Microsoft.MapPoint.PlugIns; using Microsoft.MapPoint.Rendering3D; using Microsoft.MapPoint.Rendering3D.Steps; using Microsoft.MapPoint.Rendering3D.Steps.Actors; using Microsoft.MapPoint.Binding; |
The next thing we need to do with this new plug-in is create a unique identifier. We do this using the GUID tool:
- In Visual Studio, click Tools | Create GUID.
- Select the Registry Format option.
- Click the New GUID button, and then the Copy button.
- Paste the GUID as an attribute of your class.
- Derive your class from the PlugIn class.
If this is done properly the top of your file should look something like this:
|
[Guid("83948C0A-F89A-4aa0-97E6-42687ECB6B61")] public class VE3DMouseAltitudePlugin : PlugIn |
There are three methods, Name, Activate and the Deactivate methods, that are a part of the PlugIn class which you will want to override.
|
public override string Name { get { return "Mouse Click Altitude Finder"; } }
public VE3DMouseAltitudePlugin(Host host) : base(host) { // it is encouraged that most startup logic occur in the Activate function. }
public override void Activate(object activationObject) { base.Activate(activationObject); }
public override void Deactivate() { base.Deactivate(); }
|
The next step is to add a mouse click event to the Activate method that fires when the user clicks the mouse button. We will have this mouse event call a function called “MouseClicked”, we will also call this event a “SimpleMouseClick”. The following line of code will be added to the Activate method:
|
//Add mouse click event this.Host.CommunicationManager.AttachToEvent(EngineEvents.Group, EngineEvents.OnClick, "SimpleMouseClick", MouseClicked); |
If we attach an event to the control when we activate it, we should detach it when the control deactivates. To do this, add the following line of code to the Deactivate method:
| this.Host.BindingsManager.UnregisterAction(this.BindingsSource, "SimpleMouseClick"); |
While we are at it we will change the units being used to metric (meters). To do this add the following line of code to the Activate method:
|
//Make the output of the altitude on the 3D metric this.Host.WorldEngine.UseMetric = true; |
We now have to create the MouseClicked method that gets called when the user clicks the mouse. This method will take in two parameters, a string that represents the function name that called it and a CommunicationParameter object. The MouseClicked method will look like this:
|
private void MouseClicked(string functionName, CommunicationParameter data) { } |
Inside this method we can extract information about the mouse location through the Host.Navigation.PointerPosition and Host.Navigation.PointerPositionOnObject. We can then take this extracted information and create a CommunicationParameterSet that we can send to the client side. To do this add the following code to the MouseClicked method:
|
//Verify that the mouse clicked something on the surface of the earth and not a point in the sky. if(this.Host.Navigation.PointerPosition != null) { //retrieve the altitude of the mouse above relative to sea level double altAboveSea = this.Host.Navigation.PointerPositionOnObject.Location.AltitudeAboveSeaLevel;
//retrieve the altitude of an object the mouse is on. double altAboveGround = this.Host.Navigation.PointerPositionOnObject.Location.Altitude - this.Host.Navigation.PointerPosition.Altitude;
//Create parameters that can be passed back to the client CommunicationParameter altSeaParam = new CommunicationParameter("AltitudeAboveSea", altAboveSea);
CommunicationParameter altGroundParam = new CommunicationParameter("AltitudeAboveGround", altAboveGround);
CommunicationParameter ClickedObjectParam = new CommunicationParameter("ClickedOnObject", this.Host.Navigation.PointerOnObject);
//Might as well retrieve the coordinates of where the user clicked too. CommunicationParameter latitudeParam = new CommunicationParameter("Latitude", this.Host.Navigation.PointerPosition.Location.LatitudeDegrees);
CommunicationParameter longitudeParam = new CommunicationParameter("Longitude", this.Host.Navigation.PointerPosition.Location.LongitudeDegrees);
//Create a parameter set out of the parameters CommunicationParameterSet paramSet = new CommunicationParameterSet(3); paramSet.Add(altSeaParam); paramSet.Add(altGroundParam); paramSet.Add(ClickedObjectParam); paramSet.Add(latitudeParam); paramSet.Add(longitudeParam); }
|
Note that we check that the PointerPosition has a value. The reason for this is that if the user clicks the mouse in a random point in the sky the value of the pointer position will be null. We can then throw this information in an event to be later caught on the user side. To do this add the end of following line of code to the above if statement:
|
//Fire the MouseClickAltitude Event this.Host.CommunicationManager.FireEvent(this.Guid, "MouseClickAltitude", paramSet); |
In the MouseClickAltitudeFinder.html file we will add a map like we would normally would. We will also need to load our plug-in. When the plug-in is loaded we can attach an event that gets fired on the client side when the MouseClickAltitude event gets fired in the control. This can be done with the following line of code:
|
//Add a custom event listener for a MouseClickAltitude event control3D.AttachPlugInEvent(objectPlugInGuid, "MouseClickAltitude", "MouseClickAltitude"); |
The next step is to add a MouseClickAltitude function on the client side that gets fired when the MouseClickAltitude event occurs. This function will take in two properties, an object with our data, and the map GUI ID. We can then process the returned data as we see fit. Here is what the HTML page in the example looks like:
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Mouse Click Altitude Finder</title> <script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2" type="text/javascript"></script> <script type="text/javascript"> var map; var objectPlugInGuid; var control3D; var sampleName = "VE3DMouseAltitude";
function OnPageLoad() { map = new VEMap('myMap'); //Disable Birdseye mode as it is not needed. var mapOptions = new VEMapOptions(); mapOptions.EnableBirdseye = false;
//Load the map in 3D mode map.LoadMap(null, null, VEMapStyle.Road, false, VEMapMode.Mode3D, false, null, mapOptions);
//position the map in an area that has buildings well above sea level for testing map.SetMapView(new VEMapViewSpecification(new VELatLong(39.76, -104.9917), null, 2500, -45, 0)); control3D = map.vemapcontrol.Get3DControl(); control3D.AttachEvent("OnPlugInLoaded", "On3DPlugInLoaded");
//This will need to be changed to the directory in which your pr0ject is located or the web URL were the dll exists. control3D.LoadPlugInDll("C:\\Sample\\Test\\VE3DMouseAltitude\\VE3DMouseAltitude\\bin\\Debug\\VE3DMouseAltitude.dll"); }
function On3DPlugInLoaded(data, mapguid) { // data returned from events are in JSON format, and should be processed with a JSON parser, // but eval will work for demonstration purposes var result = eval('(' + data + ')');
// we want to be sure that we are activating the correct one. var reg = new RegExp(sampleName + ".dll$"); if (result.success && reg.test(result.plugInPath)) { objectPlugInGuid = result.guid; control3D.ActivatePlugIn(objectPlugInGuid, null);
//Add a custom event listener for a MouseClickAltitude event control3D.AttachPlugInEvent(objectPlugInGuid, "MouseClickAltitude", "MouseClickAltitude");
document.getElementById('output').innerHTML = "Plugin Loaded!"; } }
//MouseClickAltitude Event Handler function MouseClickAltitude(data, mapguid) { //Verify data was returned if (data != null) { // data returned from events are in JSON format, and should be processed with a JSON parser, // but eval will work for demonstration purposes var result = eval('(' + data + ')');
//build a string to display on the browser var output = "Altitude above Sea Level: " + result.AltitudeAboveSea + " meters"; if(result.ClickedOnObject) { output += "<br/>An Object was clicked.<br/>"; output += "Altitude above Ground Level: " + result.AltitudeAboveGround + " meters"; }
output += "<br/>Latitude: " + result.Latitude + "<br/>"; output += "Longitude: " + result.Longitude;
document.getElementById('output').innerHTML = output; } } </script> </head> <body onload="OnPageLoad();"> <div id="myMap" style="position:relative;width:800px;height:600px;"></div><br /> <div id="output">Loading Plugin...</div> </body> </html>
|
We can now click points on the 3D map and find out their altitude. Complete source code can be found here: http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/VE3DMouseAltitude.zip
To get it to work you may need to re-add the references to the Microsoft.MapPoint dll’s. You will also have to change the path to the project dll in the MouseClickAltitudeFinder.html file.
Often when displaying polygons you only need or want to show the polygons that are in the viewable area of the map. By doing this an increase in performance should occur with the map control as there will be less data that will need to be handled by the control. One method to go about this is to store the bounding coordinates of your polygon compare then to the bounding box of the viewable map. This leaves you with the task of determining if two bounding boxes overlap which should be much easier than determining if a polygon with any number of sides is in the map view. What this blog is meant to do is to present a mathematical solution to this problem.
For this solution we need to break a bounding box into some key components. A bounding box has a center point and two important radii’s. A radius from the center point to and edge in the x direction, and a radius in from the center point to and edge in the y direction. The following diagram illustrates this:

By looking at this diagram we can derive two key logical statements about how to determine if two bounding boxes overlap. The first statement is that if the radius from the center of bounding box A to bounding box B
is less than or equal to the sum of the radius of A in the x direction
and the radius of B in the x direction
; then bounding boxes A and B are aligned such that they share common coordinates in the x plane. We can write this like so:
. Using this same train of thought we can make a similar statement for the y plane;
. If both of these statements are true then bound bounding boxes must overlap or share a common edge.
We can calculate the x coordinate of the center point by adding the top left x value to the radius on the bounding box in the x direction. We can then determine the distance between centers by taking the absolute value of the different between the x coordinates of the center points of bounding box A and B. This gives use the distance
which can be represented as follows:
The radius
can be calculated by taking the difference between the top left x coordinate and the bottom right x coordinate and dividing it by 2. By applying this to the above formula we get the following:
this can then be further reduced to the following:
Similarly we can can derive a formula like this for
:
Now we can look at the other side of the equation where we add the radii's of bounding box A and B. Doing this we get the following for the x direction:
this can then be reduced to the following:
Doing the same for the y direction we get the following:
this can then be reduced to the following:
We can now take the formula
and put it all together:
this can then be reduced to the following:
Lets call this condition 1.
Doing the same for the y direction we get the following:
Lets call this condition 2.
Now if both condition 1 and condition 2 are true then the bounding boxes A and B must overlap.
We can now create a simple function that takes in two VELatLongRectangle objects and returns boolean depending on if the two bounding boxes overlap.
|
function DoBoundingBoxesIntersect(bb1, bb2) {
//First bounding box, top left corner, bottom right corner var ATLx = bb1.TopLeftLatLong.Longitude; var ATLy = bb1.TopLeftLatLong.Latitude; var ABRx = bb1.BottomRightLatLong.Longitude; var ABRy = bb1.BottomRightLatLong.Latitude;
//Second bounding box, top left corner, bottom right corner var BTLx = bb2.TopLeftLatLong.Longitude; var BTLy = bb2.TopLeftLatLong.Latitude; var BBRx = bb2.BottomRightLatLong.Longitude; var BBRy = bb2.BottomRightLatLong.Latitude;
var rabx = Math.abs(ATLx + ABRx - BTLx - BBRx); var raby = Math.abs(ATLy + ABRy - BTLy - BBRy);
//rAx + rBx var raxPrbx = ABRx - ATLx + BBRx - BTLx;
//rAy + rBy var rayPrby = ATLy - ABRy + BTLy - BBRy;
if(rabx <= raxPrbx && raby <= rayPrby) { return true; } return false; }
|