Ricky's profileRicky's Bing Maps BlogBlogSkyDrive Tools Help

Blog


    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:

    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.

    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



    4/5/2009

    VE 3D Flight Simulator Version 1.1

    After having such a huge positive response from many people about the Virtual Earth 3D flight simulator I built a couple of weeks ago I decided to make some enhancements to it. The enhancements were mainly focused around improved controls for a XBox controller. I have overridden the default XBox controls that are, by default, similar to the controls for Halo and made them closer to the controls of other common flight simulators. The following controls where overridden:

    Left thumb control - Y direction:  This control used to only move you in a plane parallel to the ground and did not take the pitch of the camera into consideration. This control now allows you to accelerate in the direction the camera is pointing.

    Left thumb control - X direction: This control used to allow you to strafe to the side. This control now allows you to roll the plane.

    Right thumb control - Y direction: This control handles the pitch of the plane.

    Right thumb control - X direction: This control handles turning.

    Here is a video of the new simulator in action:  

     

    Additional enhancement was to hide the default location data that appeared in the bottom right corner of the screen. This was done by adding the following code into the Activate method of the flight simulator plug-in:

    this.Host.WorldEngine.ShowLocation = false;

     

    The new controls were defined by creating xml that could be added to the built in bindings. There are two ways to get this xml into your plug-in. One method is to create an xml file that needs to be merged with the default bindings xml file that is on the users computer. This requires the dll's of the plug-in to be installed onto the users computer and the dll to be placed in the GAC in order to get the required permissions to access the default bindings file. By using this method you can make it so that your controls are available in all instances of Virtual Earth 3D. An example of this method can be found here: http://blogs.msdn.com/virtualearth3d/archive/2008/05/01/installing-plug-ins.aspx

    The second method is to store the xml as a string inside of the plug-in and then add the xml data to the bindings. This method is much simpler and reduces a lot of the overhead.  An example of this method is used in the following article: http://blogs.msdn.com/virtualearth3d/archive/2008/07/08/animation.aspx

    This code uses the second method as it reduces the end users work when it comes to playing with the simulator.

    The new functionality for the left thumb control in the x direction required calculating the LatLong coordinate and altitude of where the plane is suppose to go to. A distance in which to travel is determined based on how much the user pushing the thumb control forward. This distance is then broken into two components, a horizontal and vertical component. These components are determined using the pitch of the camera and some trig. Using the horizontal component and the heading of the camera the destination coordinate of the plane is determined. These calculations are based on the following article: http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!400.entry

    The camera is then update by extending the CameraControl class. An example of how to do this can be found here: http://blogs.msdn.com/virtualearth3d/archive/2008/10/22/camera-control.aspx

    Complete source code for this version of the flight simulator can be downloaded here:

     http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/SimpleFlightSimulatorV1.1.zip

    3/22/2009

    Virtual Earth 3D Flight Simulator

    Update: Chck out Version 1.1 of this flight simulator here:http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!773.entry

    The Virtual Earth 3D control is a great tool when you not only want to know where some place is in the world, but also want to know what it looks like. This is particularly useful when traveling to new areas as landmarks can be easily identified ahead of time. Recently Microsoft released documentation on how to develop against this 3D API (http://blogs.msdn.com/virtualearth3d/archive/2009/01/25/documentation.aspx). Infusion development has been working with Virtual Earth 3D for well over a year now developing business applications. Most of these applications have been developed for the Microsoft Surface. Videos of various applications can be found here: http://www.youtube.com/InfusionDevelopment

    One thing I’ve noticed is that there hasn’t been a lot of development against the 3D API yet outside of the business world. This article is going to show you how to create a simple flight simulator plug-in for Virtual Earth. Here is a screen shot of the finished product to get you motivated to read through this article. You can also download the source code for this here: http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/SimpleFlightSimulator.zip

    Note you will need to re-add the references to the Virtual Earth DLL’s.

    clip_image002 

    Here is a video to show it in action. Note I was just using the keyboard for the navigation in this screen capture.

        

    Here is a video of it in action using an XBox 360 controller to navigate.  

    The first step is to install the Virtual Earth 3D control if you haven’t done so already. You can download it here: http://maps.live.com/Help/VE3DInstall/

    The second step is to create an ASP >NET Web Application project. In Visual Studios 2008 you will have to do the following steps:

    1. Open Visual Studio 2005/2008.
    2. From the File menu, choose New | Project.
    3. Choose one of the Project Types. For example, Visual C#.
    4. From the Templates list, choose Class Library.
    5. Call the project SimpleFlightSimulator and verify the project Location.
    6. Click OK to create the solution.

    The next step is to reference the Virtual Earth 3D DLL’s.

    1. From the Solution Explorer, right click the project name and click Add Reference.

    1. In the popup window, click the Browse tab and navigate to the location where you installed the Virtual Earth 3D application. (eg. C:\Program Files\Virtual Earth 3D)
    2. Hold the CTRL key down and select:
      Microsoft.MapPoint.Data
      Microsoft.MapPoint.Geometry

    Microsoft.MapPoint.Graphics3D
    Microsoft.MapPoint.Rendering3D
    Microsoft.MapPoint.Rendering3D.Utility
    Microsoft.MapPoint.UtilityPartialTrust

    4. A reference to System.Drawing, System.IO, System.Runtime.InteropServices, System.Threading and System.Reflection will also be needed.

    1. Click the OK button.

    Now you will have to create the plug-in class. This can be done by doing the following:

    1. Right click the new project's name and select Add | New Item.

    1. Under Visual C# Items | Code, select the Class item and call your class SimpleFlightSimulatorPlugin.cs
    2. Click the Add button and open the newly created file. This file will represent our plug-in for this example.
    3. The first 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.


    Part 2 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!752.entry
    Part 3 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!751.entry
    Part 4 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!750.entry


    Virtual Earth 3D Flight Simulator - part 2

    Your class should look like this:

    using System;
    using System.IO;
    using System.Drawing;
    using System.Threading;
    using System.Reflection;

    using System.Runtime.InteropServices;
    using Microsoft.MapPoint.Rendering3D.GraphicsProxy;
    using Microsoft.MapPoint.PlugIns;
    using Microsoft.MapPoint.Rendering3D;

    using Microsoft.MapPoint.Rendering3D.Steps.Actors;
    using Microsoft.MapPoint.Rendering3D.Atmospherics;
    using Microsoft.MapPoint.Binding;

    namespace SimpleFlightSimulator
    {
        [Guid("67D5CFD7-9975-492d-B7AE-1B4DE757B0BD")]
        public class SimpleFlightSimulatorPlugin : 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 "Simple Flight Simulator"; }
            } 


            public SimpleFlightSimulatorPlugin(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();

            }

    You will now create an Airplane class. This class will have three methods, Load, UpdateCockpit, and UpdateGauges. The Load method will create a ScreenImageActor to overlay the airplane cockpit imagery over the map and a ScreenTextActor to create gauge readings. The gauges will display the coordinates of the Airplane (camera), altitude, pitch (horizon) and heading. The UpdateCockpit method will allow you to change the airplane cockpit image. The UpdateGauges method will retrieve all the data required for the gauges and update the ScreenTextActor. The Airplane class should look like this:

    using System;
    using System.IO;
    using System.Drawing;
    using System.Reflection;
    using Microsoft.MapPoint;
    using Microsoft.MapPoint.Rendering3D;
    using Microsoft.MapPoint.Rendering3D.Cameras;
    using Microsoft.MapPoint.Rendering3D.Steps.Actors;
    using Microsoft.MapPoint.Geometry.VectorMath;

    namespace SimpleFlightSimulator
    {
        public class Aiplane
        {
            private ScreenTextActor gauges;
            private ScreenImageActor imageActor;
            private Host _host;

            public Aiplane(Host host)
            {
                this._host = host;
            }

            public void Load(string imageSource, Point imagePosition, Size imageSize, Color gaugeColor, Point gaugePosition)
            {
                gauges = new ScreenTextActor("GaugeReadings", "", new Font(FontFamily.GenericMonospace, 11.0f), gaugeColor, gaugePosition, null);
                this._host.Actors.Add(gauges);

                Stream data = Assembly.GetExecutingAssembly().GetManifestResourceStream(imageSource);
                Bitmap cockpit = (Bitmap)Bitmap.FromStream(data);
                data.Close();

                imageActor = new ScreenImageActor(cockpit, imagePosition, imageSize, null);
                this._host.Actors.Add(imageActor);
            }


            public void UpdateCockpit(Host host, string imageSource, Point imagePosition, Size imageSize, Color gaugeColor, Point gaugePosition)
            {
                gauges.Color = gaugeColor;
                gauges.Position = gaugePosition;

                imageActor.Size = new Size(0, 0);

                Stream data = Assembly.GetExecutingAssembly().GetManifestResourceStream(imageSource);
                Bitmap cockpit = (Bitmap)Bitmap.FromStream(data);
                data.Close();

                ScreenImageActor newImageActor = new ScreenImageActor(cockpit, imagePosition, imageSize, null);
                host.Actors.Add(newImageActor);

                imageActor = newImageActor;
            }


            public void UpdateGauges(object state)
            {
                RollPitchYaw rpy = this._host.Navigation.CameraLocalOrientation;
                GeodeticCameraSnapshot cameraSnapshot = this._host.Navigation.CameraSnapshot;
                double heading = cameraSnapshot.LocalOrientation.RollPitchYaw.Yaw * Constants.DegreesPerRadian;

                if (heading <= 0)
                    heading *= -1;
                else
                    heading = 360 - heading;

                GeodeticPositionSnapshot position = this._host.Navigation.CameraPosition;
                double pitch = rpy.Pitch * Constants.DegreesPerRadian;

                //Altitude in meters
                double altitude = position.Location.Altitude;

                this.gauges.Text = String.Format("Heading: {0:N2}º\r\nPitch: {1:N2}º\r\nAltitude: {2:N2} m\r\nLatitude: {3:N5}º\r\nLongitude: {4:N5}º",
                    heading, pitch, altitude, position.Location.LatitudeDegrees, position.Location.LongitudeDegrees);
            }
        }
    }


    Part 1 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!756.entry
    Part 3 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!751.entry
    Part 4 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!750.entry

    Virtual Earth 3D Flight Simulator - part 3

    Another class called Airport can be made to specify an Airport location. This class will have a method called FlyToHere that will cause the plane to fly to runway of the selected airport. This class should look like this:

    using System;
    using Microsoft.MapPoint;
    using Microsoft.MapPoint.Rendering3D;
    using Microsoft.MapPoint.Rendering3D.Cameras;
    using Microsoft.MapPoint.Geometry.VectorMath;

    namespace SimpleFlightSimulator
    {
        public class Airport
        {
            private LatLonAlt _latlong;
            private RollPitchYaw _rpy;

            public Airport(double latitude, double longitude, double altitude, double pitch, double heading)
            {
                this._latlong = LatLonAlt.CreateUsingDegrees(latitude, longitude, altitude);
                this._rpy = new RollPitchYaw(0, pitch, heading);
            }


            public void FlyToHere(Host host)
            {
                host.Navigation.FlyTo(this._latlong, this._rpy.Pitch, this._rpy.Yaw);
            }
        }
    }

    The next step is to create an HTML page to load Virtual Earth into. A reference to the virtual Earth map control will need to be made. In a script tag you will need to create four functions; OnPageLoad, On3DPlugInLoaded, FlyToAirport, and ChangeCockpit. The OnPageLoad function will create and load a Virtual Earth map. It will then load in the new plug-in you created. The On3DPlugInLoaded function will activate the plug-in that you created. The FlyToAirport function will allow you to specify an airport to fly to from the client side. The ChangeCockpit method will allow you to change the cockpit overlay from the client side. The script section of your HTML page should look like this:

    <title>Simple Flight Simulator</title>
    <script src="http://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 = "SimpleFlightSimulator";
    var isLoaded = false;

    function OnPageLoad() {
        map = new VEMap('myMap');
        map.LoadMap(null, 2, VEMapStyle.Hybrid, false, VEMapMode.Mode3D);

        map.HideDashboard();
        map.HideScalebar();
        control3D = map.vemapcontrol.Get3DControl();
        control3D.AttachEvent("OnPlugInLoaded", "On3DPlugInLoaded");
        control3D.LoadPlugInDll("C:\\Users\\rbrundritt\\Documents\\VE3D\\SimpleFlightSimulator\\SimpleFlightSimulator\\bin\\Debug\\SimpleFlightSimulator.dll"); // local path to where your dll lives
    }

    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);
            isLoaded = true;
        }
    }

    function FlyToAirport() {
        if (isLoaded) {
            var airport = document.getElementById('airports').options[document.getElementById('airports').selectedIndex].value;
            control3D.RaiseEvent(objectPlugInGuid, "FlyToAirport", airport);
        }
        else
            alert("Please wait for plugin to load");
    }

    function ChangeCockpit() {
        if (isLoaded) {
            var cockpit = document.getElementById('cockpits').options[document.getElementById('cockpits').selectedIndex].value;
            control3D.RaiseEvent(objectPlugInGuid, "ChangeCockpit", cockpit);
        }
        else
            alert("Please wait for plugin to load");
    }       
    </script>

    The body of your page should look like this:

    <body onload="OnPageLoad();" style="background-color:Black;">
    <table>
        <tr><td colspan="2" align="center" style="color:White;"><h2>VE 3D Simple Flight Simulator</h2></td></tr>
        <tr>
            <td align="center">
                <select id="airports">
                    <option value="LasVegas">Las Vegas International</option>
                    <option value="newyorkNewark">Newark Liberty International (New York)</option>
                    <option value="seattleTacoma">Seattle-Tacoma International</option>
                    <option value="TorontoPearson">Pearson Internalional (Toronto)</option>
                </select>
                <input type="button" value="Go To Airport" onclick="FlyToAirport();" />
            </td>
            <td align="center">
                <select id="cockpits">
                    <option value="cockpit1">Cockpit 1</option>
                    <option value="cockpit2">Cockpit 2</option>
                    <option value="cockpit3">Cockpit 3</option>
                </select>
                <input type="button" value="Change Cockpit" onclick="ChangeCockpit();" />
            </td>
        </tr>
        <tr>
            <td colspan="2" align="center">
                <div id="myMap" style="position:relative;width:1024;height:768;"></div>
            </td>
        </tr>
    </table>
    </body>


    Part 1 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!756.entry
    Part 2 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!752.entry
    Part 4 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!750.entry

    Virtual Earth 3D Flight Simulator - part 4

    You now have to create the main functionality of the plug-in. You will want to create some global Airport objects like so:

    private Airport lasVegas = new Airport(36.07639, -115.12571, 0, -14, 89.42);
    private Airport torontoPearson = new Airport(43.6738, -79.66459, 0, -14, -47.13);
    private Airport newyorkNewark = new Airport(40.67988, -74.17279, 0, -14, -24.56);
    private Airport seattleTacoma = new Airport(47.43102, -122.30807, 0, -14, -2);

    You will want to create a method to hook into the JavaScript from the client side. This can be done like so:

    private void RegisterInterfaces()
    {
        this.UnregisterInterfaces();
        if (!this.interfacesRegistered)
        {
            this.Host.CommunicationManager.AttachToEvent(this.Guid, "FlyToAirport", "FlyToAirportHandler", FlyToAirport);
            this.Host.CommunicationManager.AttachToEvent(this.Guid, "ChangeCockpit", "ChangeCockpitHandler", ChangeCockpit);
            this.interfacesRegistered = true;
        }
    }

    For good practice you should also create a method to unreferenced these hooks. This can be done like so:

    private void UnregisterInterfaces()
    {
        if (this.interfacesRegistered)
        {
            this.Host.CommunicationManager.DetachFromEvent(this.Guid, "FlyToAirport", "FlyToAirportHandler");
            this.Host.CommunicationManager.DetachFromEvent(this.Guid, "ChangeCockpit", "ChangeCockpitHandler");
            this.interfacesRegistered = false;
        }
    }

    In order to fly between airports you will need to create a method called FlyToAirport which will determine which airport you want to fly to and will navigate the camera to the runway at the selected airport. This method should like this:

    private void FlyToAirport(string functionName, object airport)
    {
        switch (airport.ToString())
        {
            case "LasVegas":
                lasVegas.FlyToHere(this.Host);
                break;
            case "TorontoPearson":
                torontoPearson.FlyToHere(this.Host);
                break;
            case "newyorkNewark":
                newyorkNewark.FlyToHere(this.Host);
                break;
            case "seattleTacoma":
                seattleTacoma.FlyToHere(this.Host);
                break;
            default:
                break;
        }
    }

    In order to change the cockpit overlay you will need to create a method called ChangeCockpit. This method will determine which cockpit the user has selected and will call the UpdateCockpit method of the airplane. This method should like this:

    private void ChangeCockpit(string functionName, object cockpit)
    {
        switch (cockpit.ToString())
        {
            case "cockpit1":
                airplane.UpdateCockpit(this.Host, "SimpleFlightSimulator.cockpit.png", new Point(0, 0), new Size(1024, 768), Color.White, new Point(755, 375));
                break;
            case "cockpit2":
                airplane.UpdateCockpit(this.Host, "SimpleFlightSimulator.cockpit2.png", new Point(0, 268), new Size(1024, 500), Color.GreenYellow, new Point(430, 375));
                break;
            case "cockpit3":
                airplane.UpdateCockpit(this.Host, "SimpleFlightSimulator.cockpit3.png", new Point(0, 0), new Size(1024, 768), Color.DarkBlue, new Point(560, 235));
                break;
            default:
                break;
        }
    }

    In the Activate method you will want to create and Airplane object and load it. You can also want to create a timer object that will call a method calls the UpdateGaage method of your airplane object. You will also want to call the RegisterInerfaces method. This method should now look like this:

    public override void Activate(object activationObject)
    {
        base.Activate(activationObject);

        //create the airplane cockpit overlay
        airplane = new Aiplane(this.Host);
        airplane.Load("SimpleFlightSimulator.cockpit.png", new Point(0, 0), new Size(1024, 768), Color.White, new Point(775, 375));

        //registers a javascript interface
        RegisterInterfaces();

        //use a timer to update the gauges every 250ms
        timer = new Timer(new TimerCallback(airplane.UpdateGauges), null, 0, 250);

        //start off at the Toronto Pearson airport
        FlyToAirport(null,"TorontoPearson");
    }

    The Deactivate method should also be updated to call the UnregisterInterfaces method and setting the timer timeout to infinite. Setting a timers timeout to infinite essentially disables it. This method should now look like this:

    public override void Deactivate()
    {
        base.Deactivate();

        //stop the timer
        timer.Change(Timeout.Infinite, 25);

        //unregister the javascript interface
        UnregisterInterfaces();
    }

     

    In order to load the cockpit images into the plug-in you will have to make them embedded resources. This can be done by right clicking on the images and going to the properties. Set the Build Action to Embedded Resource.

    The final step is to strongly name the plug-in. This is required to give the plug-in partial trust to your system. 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 TestPlugin.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 SimpleFlightSimulatorPlugin.snk file and click Open. The strong name file is automatically added to your project.
    9. Save the properties window and then close it.

    You can now build your project to compile the plug-in DLL. You can then open the HTML in a browser to view your flight simulator. To increase the sensation of flying around Virtual Earth with your own flight simulator try using an Xbox controller. The built in navigation controls of Virtual Earth recognizes the Xbox controller. The controls are similar to Halo.

    The complete source code for this application can be downloaded here:

    http://cid-e7dba9a4bfd458c5.skydrive.live.com/self.aspx/VE%20Sample%20code/SimpleFlightSimulator.zip


    Part 1 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!756.entry
    Part 2 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!752.entry
    Part 3 - http://rbrundritt.spaces.live.com/blog/cns!E7DBA9A4BFD458C5!751.entry