10/3/2009
Bing Map 3D Altitude Extraction via Mouse Click
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.