Ricky's profileRicky's Bing Maps BlogBlogSkyDrive Tools Help

Blog


    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

    3/12/2009

    Recursive Find Calls

    When using the AJAX version of the Virtual Earth control people have had issues making multiple find calls, one after the other. The resulting information does not always return in the order expected. The way around this issue is to use recursion. This will essentially make one call at a time and wait for one find call to finish before making another. The following code demonstrates how to do this:

     

    <html>
       <head>
          <title></title>
          <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
          <script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
          <script>
             var map = null;
             var count = 0;
             var locations = ["Toronto, Ontario","New York, New York","Seattle, WA"];
             function GetMap()
             {
                map = new VEMap('myMap');
                map.LoadMap();
             }  

             function FindLocation(searchstr)
             {
                if (searchstr != '')
                {
                    map.Find(null, searchstr, null, null, null, null, false, null, null, false, AddPin);
                }
             }
             function AddPin(layer, resultsArray, places, hasMore, veErrorMessage)
             { 
                if(places.length > 0)
                {
                    var shape = new VEShape( VEShapeType.Pushpin,places[0].LatLong);
                    shape.SetTitle(places[0].Name);
                    map.AddShape(shape);
                }
                count++;
                if(count<locations.length)
                    FindLocation(locations[count]);
             } 
          </script>
       </head>
    <body onload="GetMap();">
    <div id='myMap' style="position:relative; width:800px; height:600px;"></div>
    <INPUT type="button" value="Find Locations" onclick="FindLocation(locations[0]);">
    </body>
    </html>