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:

mouseClickedAltitude 
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:

  1. Open a Visual Studio Command Prompt.
  2. At the command prompt, type cd C:\Samples\Test (or the directory where you created your application) and press ENTER.
  3. At the command prompt, type sn -k MouseClickAltitudePlugin.snk and press ENTER.
  4. At the command prompt, type exit, and press ENTER.
  5. In Visual Studio, right click your project and select Properties.
  6. Click the Signing tab and check Sign the assembly.
  7. In the Choose a strong name key file drop down list, select <Browse…>
  8. Select the MouseClickAltitudePlugin.snk file and click Open. The strong name file is automatically added to your project.
  9. Save the properties window and then close it.

Your solution explorer should now look like this:

VE3DMouseAltitude

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:

  1. In Visual Studio, click Tools | Create GUID.
  2. Select the Registry Format option.
  3. Click the New GUID button, and then the Copy button.
  4. Paste the GUID as an attribute of your class.
  5. 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.

Advertisements

4 thoughts on “Bing Map 3D Altitude Extraction via Mouse Click

      • Thanks. It was stupid question :). Fine article. But in some area in US I cant see 3d models – for exanple Mastic Beach in NY. Is it possible to fix? (sorry for my english). Thanks!

      • 3D models are not available everywhere. MSFT recently announced that they are retiring the 3D control at the end of next year. After this time the control should still work but 3D models will likely stop working as the service that serves up these models will be turned off.

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