ArcObjects Library Reference  

TrackDynamicObject

About the 3D dynamic element tracking Sample

[C#]

TrackDynamicObject.cs

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.DataSourcesFile;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.SystemUI;
using ESRI.ArcGIS.GlobeCore;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Analyst3D;

namespace GlobeDynamicObjectTracking
{
  /// <summary>
  /// This command demonstrates tracking dynamic object in ArcGlobe/GlobeControl with the camera
  /// </summary>
  [Guid("DCB871A1-390A-456f-8A0D-9FDB6A20F721")]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("GlobeControlApp.TrackDynamicObject")]
  public sealed class TrackDynamicObject : BaseCommand, IDisposable
  {
    #region COM Registration Function(s)
    [ComRegisterFunction()]
    [ComVisible(false)]
    static void RegisterFunction(Type registerType)
    {
      // Required for ArcGIS Component Category Registrar support
      ArcGISCategoryRegistration(registerType);

      //
      // TODO: Add any COM registration code here
      //
    }

    [ComUnregisterFunction()]
    [ComVisible(false)]
    static void UnregisterFunction(Type registerType)
    {
      // Required for ArcGIS Component Category Registrar support
      ArcGISCategoryUnregistration(registerType);

      //
      // TODO: Add any COM unregistration code here
      //
    }

    #region ArcGIS Component Category Registrar generated code
    /// <summary>
    /// Required method for ArcGIS Component Category registration -
    /// Do not modify the contents of this method with the code editor.
    /// </summary>
    private static void ArcGISCategoryRegistration(Type registerType)
    {
      string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
      GMxCommands.Register(regKey);
      ControlsCommands.Register(regKey);

    }
    /// <summary>
    /// Required method for ArcGIS Component Category unregistration -
    /// Do not modify the contents of this method with the code editor.
    /// </summary>
    private static void ArcGISCategoryUnregistration(Type registerType)
    {
      string regKey = string.Format("HKEY_CLASSES_ROOT\\CLSID\\{{{0}}}", registerType.GUID);
      GMxCommands.Unregister(regKey);
      ControlsCommands.Unregister(regKey);

    }

    #endregion
    #endregion

    //class members
    private IGlobeHookHelper        m_globeHookHelper     = null;
    private IGlobeDisplay           m_globeDisplay        = null;
    private ISceneViewer            m_sceneViwer          = null;
    private IGlobeGraphicsLayer     m_globeGraphicsLayer  = null;
    private IRealTimeFeedManager    m_realTimeFeedManager = null;
    private IRealTimeFeed           m_realTimeFeed        = null;
    private bool                    m_bConnected          = false;
    private bool                    m_bTrackAboveTarget   = true;
    private bool                    m_once                = true;
    private int                     m_trackObjectIndex    = -1;
    private string                  m_shapefileName       = string.Empty;


    #region Ctor

    /// <summary>
    /// Class Ctor
    /// </summary>
    public TrackDynamicObject()
    {
      base.m_category = ".NET Samples";
      base.m_caption = "Track Dynamic Object";
      base.m_message = "Tracking a dynamic object";
      base.m_toolTip = "Track Dynamic Object";
      base.m_name = base.m_category + "_" + base.m_caption;

      try
      {
        string bitmapResourceName = GetType().Name + ".bmp";
        base.m_bitmap = new Bitmap(GetType(), bitmapResourceName);
      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message, "Invalid Bitmap");
      }
    }
    #endregion

    #region Overriden Class Methods

    /// <summary>
    /// Occurs when this command is created
    /// </summary>
    /// <param name="hook">Instance of the application</param>
    public override void OnCreate(object hook)
    {
      //initialize the hook-helper
      if (m_globeHookHelper == null)
        m_globeHookHelper = new GlobeHookHelper();

      //set the hook
      m_globeHookHelper.Hook = hook;

      
      //get the ArcGIS path from the registry
      RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ESRI\ArcObjectsSDK10.1");
      string path = Convert.ToString(key.GetValue("InstallDir"));

      //set the path to the featureclass used by the GPS simulator
      m_shapefileName = System.IO.Path.Combine(path, "Samples\\data\\USAMajorHighways\\usa_major_highways.shp");

      //get the GlobeDisplsy from the hook helper
      m_globeDisplay = m_globeHookHelper.GlobeDisplay;

      //initialize the real-time manager
      if (null == m_realTimeFeedManager)
        m_realTimeFeedManager = new RealTimeFeedManagerClass();

      //use the built in simulator of the real-time manager
      m_realTimeFeedManager.RealTimeFeed = m_realTimeFeedManager.RealTimeFeedSimulator as IRealTimeFeed;

      m_realTimeFeed = m_realTimeFeedManager.RealTimeFeed;
    }
    
    /// <summary>
    /// Occurs when this command is clicked
    /// </summary>
    public override void OnClick()
    {
      try
      {

        if (!m_bConnected)
        {
          //show the tracking type selection dialog (whether to track the element from above or follow it from behind)
          TrackSelectionDlg dlg = new TrackSelectionDlg();
          if (System.Windows.Forms.DialogResult.OK != dlg.ShowDialog())
            return;

          //get the required tracking mode
          m_bTrackAboveTarget = dlg.UseOrthoTrackingMode;

          //do only once initializations
          if (m_once)
          {
            //create the graphics layer to manage the dynamic object
            m_globeGraphicsLayer = new GlobeGraphicsLayerClass();
            ((ILayer)m_globeGraphicsLayer).Name = "DynamicObjects";

            IScene scene = (IScene)m_globeDisplay.Globe;

            //add the new graphic layer to the globe
            scene.AddLayer((ILayer)m_globeGraphicsLayer, false);

            //activate the graphics layer
            scene.ActiveGraphicsLayer = (ILayer)m_globeGraphicsLayer;

            //redraw the GlobeDisplay
            m_globeDisplay.RefreshViewers();

            //open a polyline featurelayer that would serve the real-time feed GPS simulator
            IFeatureLayer featureLayer = GetFeatureLayer();
            if (featureLayer == null)
              return;
            
            //assign the featurelayer to the GPS simulator
            m_realTimeFeedManager.RealTimeFeedSimulator.FeatureLayer = featureLayer;

            m_once = false;
          }

          //get the GlobeViewUtil which is needed for coordinate transformations
          m_sceneViwer = m_globeDisplay.ActiveViewer;

          //Set the globe mode to terrain mode, since otherwise it will not be possible to set the target position
         ((IGlobeCamera)m_sceneViwer.Camera).OrientationMode = esriGlobeCameraOrientationMode.esriGlobeCameraOrientationLocal;


          //set the simulator elapsed time
          m_realTimeFeedManager.RealTimeFeedSimulator.TimeIncrement = 0.1; //sec

          //wire the real-time feed PositionUpdate event
          ((IRealTimeFeedEvents_Event)m_realTimeFeed).PositionUpdated += new IRealTimeFeedEvents_PositionUpdatedEventHandler(OnPositionUpdated);

          //start the real-time listener
          m_realTimeFeed.Start();
        }
        else
        {
          //stop the real-time listener
          m_realTimeFeed.Stop();

          //un-wire the PositionUpdated event handler
          ((IRealTimeFeedEvents_Event)m_realTimeFeed).PositionUpdated -= new IRealTimeFeedEvents_PositionUpdatedEventHandler(OnPositionUpdated);
        }

        //switch the connection flag
        m_bConnected = !m_bConnected;
      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message);
      }
    }

    /// <summary>
    /// The Checked property indicates the state of this Command. 
    /// </summary>
    /// <remarks>If a command item appears depressed on a commandbar, the command is checked.</remarks>
    public override bool Checked
    {
      get
      {
        return m_bConnected;
      }
    }

    #endregion

    #region helper methods

    /// <summary>
    /// get a featurelayer that would be used by the real-time simulator
    /// </summary>
    /// <returns></returns>
    private IFeatureLayer GetFeatureLayer()
    {
      //instantiate a new featurelayer
      IFeatureLayer featureLayer = new FeatureLayerClass();     

      //set the layer's name
      featureLayer.Name = "GPS Data";

      //open the featureclass
      IFeatureClass featureClass = OpenFeatureClass();
      if (featureClass == null)
        return null;

      //set the featurelayer featureclass
      featureLayer.FeatureClass = featureClass;

      //return the featurelayer
      return featureLayer;
    }

    /// <summary>
    /// Opens a shapefile polyline featureclass
    /// </summary>
    /// <returns></returns>
    private IFeatureClass OpenFeatureClass()
    {

      string fileName = System.IO.Path.GetFileNameWithoutExtension(m_shapefileName);
      
      //instantiate a new workspace factory
      IWorkspaceFactory workspaceFactory = new ShapefileWorkspaceFactoryClass();

      //get the workspace directory
      string path = System.IO.Path.GetDirectoryName(m_shapefileName);

      //open the workspace containing the featureclass
      IFeatureWorkspace featureWorkspace = workspaceFactory.OpenFromFile(path, 0) as IFeatureWorkspace;

      //open the featureclass
      IFeatureClass featureClass = featureWorkspace.OpenFeatureClass(fileName);

      //make sure that the featureclass type is polyline
      if (featureClass.ShapeType != esriGeometryType.esriGeometryPolyline)
      {
        featureClass = null;
      }

      //return the featureclass
      return featureClass;
    }

    /// <summary>
    /// Adds a sphere element to the given graphics layer at the specified position
    /// </summary>
    /// <param name="globeGraphicsLayer"></param>
    /// <param name="position"></param>
    /// <returns></returns>
    private int AddTrackElement(IGlobeGraphicsLayer globeGraphicsLayer, esriGpsPositionInfo position)
    {
      if (null == globeGraphicsLayer)
        return -1;

      //create a new point at the given position
      IPoint point = new PointClass();
      ((IZAware)point).ZAware = true;
      point.X = position.longitude;
      point.Y = position.latitude;
      point.Z = 0.0;

      //set the color for the element (red)
      IRgbColor color = new RgbColorClass();
      color.Red = 255;
      color.Green = 0;
      color.Blue = 0;

      //create a new 3D marker symbol
      IMarkerSymbol markerSymbol = new SimpleMarker3DSymbolClass();

      //set the marker symbol's style and resolution
      ((ISimpleMarker3DSymbol)markerSymbol).Style = esriSimple3DMarkerStyle.esriS3DMSSphere;
      ((ISimpleMarker3DSymbol)markerSymbol).ResolutionQuality = 1.0;

      //set the symbol's size and color
      markerSymbol.Size = 700;
      markerSymbol.Color = color as IColor;

      //crate the graphic element
      IElement trackElement = new MarkerElementClass();

      //set the element's symbol and geometry (location and shape)
      ((IMarkerElement)trackElement).Symbol = markerSymbol;
      trackElement.Geometry = point as IPoint;


      //add the element to the graphics layer
      int elemIndex = 0;
      ((IGraphicsContainer)globeGraphicsLayer).AddElement(trackElement, 0);

      //get the element's index
      globeGraphicsLayer.FindElementIndex(trackElement, out elemIndex);
      return elemIndex;
    }

    /// <summary>
    /// The real-time feed position updated event handler
    /// </summary>
    /// <param name="position">a GPS position information</param>
    /// <param name="estimate">indicates whether this is an estimated time or real time</param>
    void OnPositionUpdated(ref esriGpsPositionInfo position, bool estimate)
    {
      try
      {
        //add the tracking element to the tracking graphics layer (should happen only once)
        if (-1 == m_trackObjectIndex)
        {
          int index = AddTrackElement(m_globeGraphicsLayer, position);
          if (-1 == index)
            throw new Exception("could not add tracking object");

          //cache the element's index
          m_trackObjectIndex = index;

          return;
        }
        //get the element by its index
        IElement elem = ((IGraphicsContainer3D)m_globeGraphicsLayer).get_Element(m_trackObjectIndex);

        //keep the previous location
        double lat, lon, alt;
        ((IPoint)elem.Geometry).QueryCoords(out lon, out lat);
        alt = ((IPoint)elem.Geometry).Z;

        //update the element's position
        IPoint point = elem.Geometry as IPoint;
        point.X = position.longitude;
        point.Y = position.latitude;
        point.Z = alt;
        elem.Geometry = (IGeometry)point;

        //update the element in the graphics layer.
        lock (m_globeGraphicsLayer)
        {
          m_globeGraphicsLayer.UpdateElementByIndex(m_trackObjectIndex);
        }

        IGlobeCamera globeCamera = m_sceneViwer.Camera as IGlobeCamera;

        //set the camera position in order to track the element
        if (m_bTrackAboveTarget)
          TrackAboveTarget(globeCamera, point);
        else
          TrackFollowTarget(globeCamera, point.X, point.Y, point.Z, lon, lat, alt);

      }
      catch (Exception ex)
      {
        System.Diagnostics.Trace.WriteLine(ex.Message);
      }
    }
    void TrackDynamicObject_PositionUpdated(ref esriGpsPositionInfo position, bool estimate)
    {
      
    }

    /// <summary>
    /// If the user chose to track the element from behind, set the camera behind the element 
    /// so that the camera will be placed on the line connecting the previous and the current element's position.
    /// </summary>
    /// <param name="globeCamera"></param>
    /// <param name="newLon"></param>
    /// <param name="newLat"></param>
    /// <param name="newAlt"></param>
    /// <param name="oldLon"></param>
    /// <param name="oldLat"></param>
    /// <param name="oldAlt"></param>
    private void TrackFollowTarget(IGlobeCamera globeCamera, double newLon, double newLat, double newAlt, double oldLon, double oldLat, double oldAlt)
    {
      //make sure that the camera position is not directly above the element. Otherwise it can lead to
      //an ill condition
      if (newLon == oldLon && newLat == oldLat)
      {
        newLon += 0.00001;
        newLat += 0.00001;
      }

      //calculate the azimuth from the previous position to the current position
      double azimuth = Math.Atan2(newLat - oldLat, newLon - oldLon) * (Math.PI / 180.0);

      //the camera new position, right behind the element
      double obsX = newLon - 0.04 * Math.Cos(azimuth * (Math.PI / 180));
      double obsY = newLat - 0.04 * Math.Sin(azimuth * (Math.PI / 180));

      //set the camera position. The camera must be locked in order to prevent a dead-lock caused by the cache manager
      lock (globeCamera)
      {
        globeCamera.SetTargetLatLonAlt(newLat, newLon, newAlt / 1000.0);
        globeCamera.SetObserverLatLonAlt(obsY, obsX, newAlt / 1000.0 + 0.7);
        m_sceneViwer.Camera.Apply();
      }

      //refresh the globe display
      m_globeDisplay.RefreshViewers();
    }

    /// <summary>
    /// should the user choose to track the element from above, set the camera above the element
    /// </summary>
    /// <param name="globeCamera"></param>
    /// <param name="objectLocation"></param>
    private void TrackAboveTarget(IGlobeCamera globeCamera, IPoint objectLocation)
    {
      //Update the observer as well as the camera position
      //The camera must be locked in order to prevent a dead-lock caused by the cache manager
      lock (globeCamera)
      {

        globeCamera.SetTargetLatLonAlt(objectLocation.Y, objectLocation.X, objectLocation.Z / 1000.0);

        //The camera must nut be located exactly above the target, since it results in poor orientation computation
        //and therefore the camera gets jumpy.
        globeCamera.SetObserverLatLonAlt(objectLocation.Y - 0.000001, objectLocation.X - 0.000001, objectLocation.Z / 1000.0 + 30.0);
        m_sceneViwer.Camera.Apply();
      }
      m_globeDisplay.RefreshViewers();
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
    }

    #endregion
  }
}

[Visual Basic .NET]

TrackDynamicObject.vb

Imports Microsoft.VisualBasic
Imports System
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports Microsoft.Win32
Imports ESRI.ArcGIS.ADF.BaseClasses
Imports ESRI.ArcGIS.ADF.CATIDs
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.DataSourcesFile
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.SystemUI
Imports ESRI.ArcGIS.GlobeCore
Imports ESRI.ArcGIS.Controls
Imports ESRI.ArcGIS.Analyst3D

''' <summary>
''' This command demonstrates tracking dynamic object in ArcGlobe/GlobeControl with the camera
''' </summary>
<Guid("DCB871A1-390A-456f-8A0D-9FDB6A20F721"), ClassInterface(ClassInterfaceType.None), ProgId("GlobeControlApp.TrackDynamicObject")> _
Public NotInheritable Class TrackDynamicObject : Inherits BaseCommand : Implements IDisposable
#Region "COM Registration Function(s)"
  <ComRegisterFunction(), ComVisible(False)> _
  Private Shared Sub RegisterFunction(ByVal registerType As Type)
    ' Required for ArcGIS Component Category Registrar support
    ArcGISCategoryRegistration(registerType)

    '
    ' TODO: Add any COM registration code here
    ''
  End Sub

  <ComUnregisterFunction(), ComVisible(False)> _
  Private Shared Sub UnregisterFunction(ByVal registerType As Type)
    ' Required for ArcGIS Component Category Registrar support
    ArcGISCategoryUnregistration(registerType)

    '
    ' TODO: Add any COM unregistration code here
    ''
  End Sub

#Region "ArcGIS Component Category Registrar generated code"
  ''' <summary>
  ''' Required method for ArcGIS Component Category registration -
  ''' Do not modify the contents of this method with the code editor.
  ''' </summary>
  Private Shared Sub ArcGISCategoryRegistration(ByVal registerType As Type)
    Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
    GMxCommands.Register(regKey)
    ControlsCommands.Register(regKey)

  End Sub
  ''' <summary>
  ''' Required method for ArcGIS Component Category unregistration -
  ''' Do not modify the contents of this method with the code editor.
  ''' </summary>
  Private Shared Sub ArcGISCategoryUnregistration(ByVal registerType As Type)
    Dim regKey As String = String.Format("HKEY_CLASSES_ROOT\CLSID\{{{0}}}", registerType.GUID)
    GMxCommands.Unregister(regKey)
    ControlsCommands.Unregister(regKey)

  End Sub

#End Region
#End Region

  'class members
  Private m_globeHookHelper As IGlobeHookHelper = Nothing
  Private m_globeDisplay As IGlobeDisplay = Nothing
  Private m_sceneViwer As ISceneViewer = Nothing
  Private m_globeGraphicsLayer As IGlobeGraphicsLayer = Nothing
  Private m_realTimeFeedManager As IRealTimeFeedManager = Nothing
  Private m_realTimeFeed As IRealTimeFeed = Nothing
  Private m_bConnected As Boolean = False
  Private m_bTrackAboveTarget As Boolean = True
  Private m_once As Boolean = True
  Private m_trackObjectIndex As Integer = -1
  Private m_shapefileName As String = String.Empty


#Region "class constructor"

  ''' <summary>
  ''' Class Ctor
  ''' </summary>
  Public Sub New()
    MyBase.m_category = ".NET Samples"
    MyBase.m_caption = "Track Dynamic Object"
    MyBase.m_message = "Tracking a dynamic object"
    MyBase.m_toolTip = "Track Dynamic Object"
    MyBase.m_name = MyBase.m_category & "_" & MyBase.m_caption

    Try
      Dim bitmapResourceName As String = Me.GetType().Name & ".bmp"
      MyBase.m_bitmap = New Bitmap(Me.GetType(), bitmapResourceName)
    Catch ex As Exception
      System.Diagnostics.Trace.WriteLine(ex.Message, "Invalid Bitmap")
    End Try
  End Sub
#End Region

#Region "Overridden Class Methods"

  ''' <summary>
  ''' Occurs when this command is created
  ''' </summary>
  ''' <param name="hook">Instance of the application</param>
  Public Overrides Sub OnCreate(ByVal hook As Object)
    'initialize the hook-helper
    If m_globeHookHelper Is Nothing Then
      m_globeHookHelper = New GlobeHookHelper()
    End If

    'set the hook
    m_globeHookHelper.Hook = hook

    'connect to the ZipCodes featureclass
    'get the ArcGIS path from the registry
        Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\ESRI\ArcObjectsSDK10.1")
    Dim path As String = Convert.ToString(key.GetValue("InstallDir"))

    'set the path to the featureclass used by the GPS simulator
    m_shapefileName = System.IO.Path.Combine(path, "Samples\\data\\USAMajorHighways\\usa_major_highways.shp")

    'get the GlobeDisplsy from the hook helper
    m_globeDisplay = m_globeHookHelper.GlobeDisplay

    'initialize the real-time manager
    If Nothing Is m_realTimeFeedManager Then
      m_realTimeFeedManager = New RealTimeFeedManagerClass()
    End If

    'use the built in simulator of the real-time manager
    m_realTimeFeedManager.RealTimeFeed = TryCast(m_realTimeFeedManager.RealTimeFeedSimulator, IRealTimeFeed)

    'keep a reference to the RealTimeManager in order to prevent the garbage collector to try and dispose it
    m_realTimeFeed = m_realTimeFeedManager.RealTimeFeed
  End Sub

  ''' <summary>
  ''' Occurs when this command is clicked
  ''' </summary>
  Public Overrides Sub OnClick()
    Try

      If (Not m_bConnected) Then
        'show the tracking type selection dialog (whether to track the element from above or follow it from behind)
        Dim dlg As TrackSelectionDlg = New TrackSelectionDlg()
        If System.Windows.Forms.DialogResult.OK <> dlg.ShowDialog() Then
          Return
        End If

        'get the required tracking mode
        m_bTrackAboveTarget = dlg.UseOrthoTrackingMode

        'do only once initializations
        If m_once Then
          'create the graphics layer to manage the dynamic object
          m_globeGraphicsLayer = New GlobeGraphicsLayerClass()
          CType(m_globeGraphicsLayer, ILayer).Name = "DynamicObjects"

          Dim scene As IScene = CType(m_globeDisplay.Globe, IScene)

          'add the new graphic layer to the globe
          scene.AddLayer(CType(m_globeGraphicsLayer, ILayer), False)

          'activate the graphics layer
          scene.ActiveGraphicsLayer = CType(m_globeGraphicsLayer, ILayer)

          'open a polyline featurelayer that would serve the real-time feed GPS simulator
          Dim featureLayer As IFeatureLayer = GetFeatureLayer()
          If featureLayer Is Nothing Then
            Return
          End If

          'assign the featurelayer to the GPS simulator
          m_realTimeFeedManager.RealTimeFeedSimulator.FeatureLayer = featureLayer

          m_once = False
        End If

        'get the GlobeViewUtil which is needed for coordinate transformations
        m_sceneViwer = m_globeDisplay.ActiveViewer

        'Set the globe mode to terrain mode, since otherwise it will not be possible to set the target position
        CType(m_sceneViwer.Camera, IGlobeCamera).OrientationMode = esriGlobeCameraOrientationMode.esriGlobeCameraOrientationLocal

        'set the simulator elapsed time
        m_realTimeFeedManager.RealTimeFeedSimulator.TimeIncrement = 0.1 'sec

        'wire the real-time feed PositionUpdate event
        AddHandler (CType(m_realTimeFeed, IRealTimeFeedEvents_Event)).PositionUpdated, AddressOf OnPositionUpdated

        'start the real-time listener
        m_realTimeFeed.Start()
      Else
        'stop the real-time listener
        m_realTimeFeed.Stop()

        'unhook the PositionUpdated event handler
        RemoveHandler (CType(m_realTimeFeed, IRealTimeFeedEvents_Event)).PositionUpdated, AddressOf TrackDynamicObject_PositionUpdated
      End If

      'switch the connection flag
      m_bConnected = Not m_bConnected
    Catch ex As Exception
      System.Diagnostics.Trace.WriteLine(ex.Message)
    End Try
  End Sub

  ''' <summary>
  ''' The Checked property indicates the state of this Command. 
  ''' </summary>
  ''' <remarks>If a command item appears depressed on a commandbar, the command is checked.</remarks>
  Public Overrides ReadOnly Property Checked() As Boolean
    Get
      Return m_bConnected
    End Get
  End Property

#End Region

#Region "helper methods"

  ''' <summary>
  ''' get a featurelayer that would be used by the real-time simulator
  ''' </summary>
  ''' <returns></returns>
  Private Function GetFeatureLayer() As IFeatureLayer
    'instantiate a new featurelayer
    Dim featureLayer As IFeatureLayer = New FeatureLayerClass()

    'set the layer's name
    featureLayer.Name = "GPS Data"

    'open the featureclass
    Dim featureClass As IFeatureClass = OpenFeatureClass()
    If featureClass Is Nothing Then
      Return Nothing
    End If

    'set the featurelayer featureclass
    featureLayer.FeatureClass = featureClass

    'return the featurelayer
    Return featureLayer
  End Function

  ''' <summary>
  ''' Opens a shapefile polyline featureclass
  ''' </summary>
  ''' <returns></returns>
  Private Function OpenFeatureClass() As IFeatureClass
    Dim path As String = System.IO.Path.GetDirectoryName(m_shapefileName)
    Dim fileName As String = System.IO.Path.GetFileNameWithoutExtension(m_shapefileName)
    'instantiate a new workspace factory
    Dim workspaceFactory As IWorkspaceFactory = New ShapefileWorkspaceFactoryClass()

    'open the workspace containing the featureclass
    Dim featureWorkspace As IFeatureWorkspace = TryCast(workspaceFactory.OpenFromFile(path, 0), IFeatureWorkspace)

    'open the featureclass
    Dim featureClass As IFeatureClass = featureWorkspace.OpenFeatureClass(fileName)

    'make sure that the featureclass type is polyline
    If featureClass.ShapeType <> esriGeometryType.esriGeometryPolyline Then
      featureClass = Nothing
    End If

    'return the featureclass
    Return featureClass
  End Function

  ''' <summary>
  ''' Adds a sphere element to the given graphics layer at the specified position
  ''' </summary>
  ''' <param name="globeGraphicsLayer"></param>
  ''' <param name="position"></param>
  ''' <returns></returns>
  Private Function AddTrackElement(ByVal globeGraphicsLayer As IGlobeGraphicsLayer, ByVal position As esriGpsPositionInfo) As Integer
    If Nothing Is globeGraphicsLayer Then
      Return -1
    End If

    'create a new point at the given position
    Dim point As IPoint = New PointClass()
    CType(point, IZAware).ZAware = True
    point.X = position.longitude
    point.Y = position.latitude
    point.Z = 0.0

    'set the color for the element (red)
    Dim color As IRgbColor = New RgbColorClass()
    color.Red = 255
    color.Green = 0
    color.Blue = 0

    'create a new 3D marker symbol
    Dim markerSymbol As IMarkerSymbol = New SimpleMarker3DSymbolClass()

    'set the marker symbol's style and resolution
    CType(markerSymbol, ISimpleMarker3DSymbol).Style = esriSimple3DMarkerStyle.esriS3DMSSphere
    CType(markerSymbol, ISimpleMarker3DSymbol).ResolutionQuality = 1.0

    'set the symbol's size and color
    markerSymbol.Size = 700
    markerSymbol.Color = TryCast(color, IColor)

    'crate the graphic element
    Dim trackElement As IElement = New MarkerElementClass()

    'set the element's symbol and geometry (location and shape)
    CType(trackElement, IMarkerElement).Symbol = markerSymbol
    trackElement.Geometry = TryCast(point, IPoint)


    'add the element to the graphics layer
    Dim elemIndex As Integer = 0
    CType(globeGraphicsLayer, IGraphicsContainer).AddElement(trackElement, 0)

    'get the element's index
    globeGraphicsLayer.FindElementIndex(trackElement, elemIndex)
    Return elemIndex
  End Function

  ''' <summary>
  ''' The real-time feed position updated event handler
  ''' </summary>
  ''' <param name="position">a GPS position information</param>
  ''' <param name="estimate">indicates whether this is an estimated time or real time</param>
  Private Sub OnPositionUpdated(ByRef position As esriGpsPositionInfo, ByVal estimate As Boolean)
    Try
      'add the tracking element to the tracking graphics layer (should happen only once)
      If -1 = m_trackObjectIndex Then
        Dim index As Integer = AddTrackElement(m_globeGraphicsLayer, position)
        If -1 = index Then
          Throw New Exception("could not add tracking object")
        End If

        'cache the element's index
        m_trackObjectIndex = index

        Return
      End If
      'get the element by its index
      Dim elem As IElement = (CType(m_globeGraphicsLayer, IGraphicsContainer3D)).Element(m_trackObjectIndex)

      'keep the previous location
      Dim lat, lon, alt As Double
      CType(elem.Geometry, IPoint).QueryCoords(lon, lat)
      alt = (CType(elem.Geometry, IPoint)).Z

      'update the element's position
      Dim point As IPoint = TryCast(elem.Geometry, IPoint)
      point.X = position.longitude
      point.Y = position.latitude
      point.Z = alt
      elem.Geometry = CType(point, IGeometry)

      'update the element in the graphics layer.
      SyncLock m_globeGraphicsLayer
        m_globeGraphicsLayer.UpdateElementByIndex(m_trackObjectIndex)
      End SyncLock

      Dim globeCamera As IGlobeCamera = TryCast(m_sceneViwer.Camera, IGlobeCamera)

      'set the camera position in order to track the element
      If m_bTrackAboveTarget Then
        TrackAboveTarget(globeCamera, point)
      Else
        TrackFollowTarget(globeCamera, point.X, point.Y, point.Z, lon, lat, alt)
      End If

    Catch ex As Exception
      System.Diagnostics.Trace.WriteLine(ex.Message)
    End Try
  End Sub
  Private Sub TrackDynamicObject_PositionUpdated(ByRef position As esriGpsPositionInfo, ByVal estimate As Boolean)

  End Sub

  ''' <summary>
  ''' If the user chose to track the element from behind, set the camera behind the element 
  ''' so that the camera will be placed on the line connecting the previous and the current element's position.
  ''' </summary>
  ''' <param name="globeCamera"></param>
  ''' <param name="newLon"></param>
  ''' <param name="newLat"></param>
  ''' <param name="newAlt"></param>
  ''' <param name="oldLon"></param>
  ''' <param name="oldLat"></param>
  ''' <param name="oldAlt"></param>
  Private Sub TrackFollowTarget(ByVal globeCamera As IGlobeCamera, ByVal newLon As Double, ByVal newLat As Double, ByVal newAlt As Double, ByVal oldLon As Double, ByVal oldLat As Double, ByVal oldAlt As Double)
    'make sure that the camera position is not directly above the element. Otherwise it can lead to
    'an ill condition
    If newLon = oldLon AndAlso newLat = oldLat Then
      newLon += 0.00001
      newLat += 0.00001
    End If

    'calculate the azimuth from the previous position to the current position
    Dim azimuth As Double = Math.Atan2(newLat - oldLat, newLon - oldLon) * (Math.PI / 180.0)

    'the camera new position, right behind the element
    Dim obsX As Double = newLon - 0.04 * Math.Cos(azimuth * (Math.PI / 180))
    Dim obsY As Double = newLat - 0.04 * Math.Sin(azimuth * (Math.PI / 180))

    'set the camera position. The camera must be locked in order to prevent a dead-lock caused by the cache manager
    SyncLock globeCamera
      globeCamera.SetTargetLatLonAlt(newLat, newLon, newAlt / 1000.0)
      globeCamera.SetObserverLatLonAlt(obsY, obsX, newAlt / 1000.0 + 0.7)
      m_sceneViwer.Camera.Apply()
    End SyncLock

    'refresh the globe display
    m_globeDisplay.RefreshViewers()
  End Sub

  ''' <summary>
  ''' should the user choose to track the element from above, set the camera above the element
  ''' </summary>
  ''' <param name="globeCamera"></param>
  ''' <param name="objectLocation"></param>
  Private Sub TrackAboveTarget(ByVal globeCamera As IGlobeCamera, ByVal objectLocation As IPoint)
    'Update the observer as well as the camera position
    'The camera must be locked in order to prevent a dead-lock caused by the cache manager
    SyncLock globeCamera

      globeCamera.SetTargetLatLonAlt(objectLocation.Y, objectLocation.X, objectLocation.Z / 1000.0)

      'The camera must nut be located exactly above the target, since it results in poor orientation computation
      'and therefore the camera gets jumpy.
      globeCamera.SetObserverLatLonAlt(objectLocation.Y - 0.000001, objectLocation.X - 0.000001, objectLocation.Z / 1000.0 + 30.0)
      m_sceneViwer.Camera.Apply()
    End SyncLock
    m_globeDisplay.RefreshViewers()
  End Sub

#End Region

#Region "IDisposable Members"

  Public Sub Dispose() Implements IDisposable.Dispose

  End Sub

#End Region
End Class