ArcObjects Library Reference  

DynamicBikingCmd

About the Dynamic biking Sample

[C#]

DynamicBikingCmd.cs

using System;
using System.IO;
using System.Drawing;
using System.Xml;
using System.Xml.XPath;
using System.Threading;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;

namespace DynamicBiking
{
  /// <summary>
  /// Summary description for DynamicBikingCmd.
  /// </summary>
  [Guid("f01054d2-0130-4124-8436-1bf2942bf2b6")]
  [ClassInterface(ClassInterfaceType.None)]
  [ProgId("DynamicBiking.DynamicBikingCmd")]
  public sealed class DynamicBikingCmd : BaseCommand
  {
    #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);
      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);
      ControlsCommands.Unregister(regKey);

    }

    #endregion
    #endregion

    #region class members
    private enum GPSPlaybackFormat
    {
      HST = 0,
      GPX = 1,
      XML = 2
    }
    private GPSPlaybackFormat m_playbackFormat = GPSPlaybackFormat.GPX;
    private IHookHelper m_hookHelper;
    private IDynamicMap m_dynamicMap = null;
    private IActiveView m_activeView = null;
    private bool m_bConnected = false;

    private IPoint m_gpsPosition = null;
    private IPoint m_additionalInfoPoint = null;
    private IPointCollection4 m_bikeRouteGeometry = null;
    private IGeometryBridge2 m_geometryBridge = null;
    private WKSPoint[] m_wksPoints = new WKSPoint[1];
    private WKSPoint m_wksPrevPosition;

    private IDynamicSymbolProperties2 m_dynamicSymbolProperties = null;
    private IDynamicCompoundMarker2 m_dynamicCompoundMarker = null;
    private IDynamicScreenDisplay m_dynamicScreenDisplay = null;
    private IDynamicGlyph m_bikeGlyph = null;
    private IDynamicGlyph m_bikeRouteGlyph = null;
    private IDynamicGlyph m_textGlyph = null;
    private IDynamicGlyph m_catGlyph = null;
    private IDynamicGlyph m_gpsGlyph = null;
    private IDynamicGlyph[] m_heartRateGlyph;
		private float m_gpsSymbolScale = 1.0f;
    private double m_heading = 0;
    private string m_heartRateString = string.Empty;
    private string m_altitudeString = string.Empty;
    private string m_speed = string.Empty;
    private bool m_bOnce = true;
    private int m_heartRateCounter = 0;
    private int m_drawCycles = 0;
    public int m_playbackSpeed = 10;
    private bool m_bTrackMode = false;
		string[] nullString = null;

    private string m_xmlPath = string.Empty;
    // xml loader thread stuff
    private Thread m_dataLoaderThread = null;
    private static AutoResetEvent m_autoEvent = new AutoResetEvent(false);
    private int m_bikePositionCount = 0;

    private sealed class BikePositionInfo
    {
      public BikePositionInfo()
      {

      }

      public WKSPoint position;
      public DateTime time;
      public double altitudeMeters;
      public int heartRate;
      public int lapCount;
      public int lapAverageHeartRate;
      public int lapMaximumHeartRate;
      public int lapCalories;
      public double lapMaximumSpeed;
      public double lapDistanceMeters;
      public double course;
      public double speed;
      public int positionCount;
    }
    private BikePositionInfo m_bikePositionInfo = null;

    private struct XmlDocTaksData
    {
      public string xmlDocPath;
    }

    #endregion

    #region class constructor
    public DynamicBikingCmd()
    {
      base.m_category = ".NET Samples";
      base.m_caption = "Dynamic Biking";
      base.m_message = "Dynamic Biking";
      base.m_toolTip = "Dynamic Biking";
      base.m_name = "DynamicBiking_DynamicBikingCmd";

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

    ~DynamicBikingCmd()
    {
      if (m_dataLoaderThread != null && m_dataLoaderThread.ThreadState == ThreadState.Running)
      {
        m_autoEvent.Set();
        m_dataLoaderThread.Join();
      }
    }
    #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)
    {
      if (hook == null)
        return;

      if (m_hookHelper == null)
        m_hookHelper = new HookHelperClass();

      m_hookHelper.Hook = hook;

      m_activeView = m_hookHelper.ActiveView;

      m_geometryBridge = new GeometryEnvironmentClass();

      m_wksPrevPosition.X = 0;
      m_wksPrevPosition.Y = 0;
    }

    /// <summary>
    /// Occurs when this command is clicked
    /// </summary>
    public override void OnClick()
    {
      m_dynamicMap = m_hookHelper.FocusMap as IDynamicMap;
      if (m_dynamicMap == null)
        return;

      if (!m_dynamicMap.DynamicMapEnabled)
      {
        MessageBox.Show("Please enable dynamic mode and try again.");
        return;
      }

      if (!m_bConnected)
      {
        m_xmlPath = GetPlaybackXmlPath();
        if (m_xmlPath == string.Empty)
          return;

        m_bikePositionInfo = new BikePositionInfo();
        m_bikePositionInfo.positionCount = m_bikePositionCount;
        m_bikePositionInfo.altitudeMeters = 0;
        m_bikePositionInfo.time = DateTime.Now;
        m_bikePositionInfo.position.X = 0;
        m_bikePositionInfo.position.Y = 0;
        m_bikePositionInfo.heartRate = 0;
        m_bikePositionInfo.lapCount = 0;
        m_bikePositionInfo.lapAverageHeartRate = 0;
        m_bikePositionInfo.lapMaximumHeartRate = 0;
        m_bikePositionInfo.lapDistanceMeters = 0;
        m_bikePositionInfo.lapMaximumSpeed = 0;
        m_bikePositionInfo.lapCalories = 0;

        m_gpsPosition = new PointClass();
        m_additionalInfoPoint = new PointClass();
        m_additionalInfoPoint.PutCoords(70, 90);
        m_bikeRouteGeometry = new PolylineClass();

        // wire dynamic map events
        ((IDynamicMapEvents_Event)m_dynamicMap).AfterDynamicDraw += new IDynamicMapEvents_AfterDynamicDrawEventHandler(OnAfterDynamicDraw);
        ((IDynamicMapEvents_Event)m_dynamicMap).DynamicMapStarted += new IDynamicMapEvents_DynamicMapStartedEventHandler(OnDynamicMapStarted);

        // spin the thread that plays the data from the xml file
        m_dataLoaderThread = new Thread(new ParameterizedThreadStart(XmlReaderTask));

        XmlDocTaksData taskData;
        taskData.xmlDocPath = m_xmlPath;
        m_dataLoaderThread.Start(taskData);
      }
      else
      {
        // unwire wire dynamic map events
        ((IDynamicMapEvents_Event)m_dynamicMap).AfterDynamicDraw -= new IDynamicMapEvents_AfterDynamicDrawEventHandler(OnAfterDynamicDraw);
        ((IDynamicMapEvents_Event)m_dynamicMap).DynamicMapStarted -= new IDynamicMapEvents_DynamicMapStartedEventHandler(OnDynamicMapStarted);

        // force the bike xml playback thread to quite
        m_autoEvent.Set();
        m_dataLoaderThread.Join();

        System.Diagnostics.Trace.WriteLine("Done!!!");
      }

      m_bConnected = !m_bConnected;
    }

    public override bool Checked
    {
      get
      {
        return m_bConnected;
      }
    }

    #endregion

    #region public properties
    public bool IsPlaying
    {
      get { return m_bConnected; }
    }

    public bool TrackMode
    {
      get { return m_bTrackMode; }
      set { m_bTrackMode = value; }
    }

    public int PlaybackSpeed
    {
      get { return m_playbackSpeed; }
      set { m_playbackSpeed = value; }
    }
    #endregion

    #region Private methods
    private void OnAfterDynamicDraw(esriDynamicMapDrawPhase DynamicMapDrawPhase, IDisplay Display, IDynamicDisplay dynamicDisplay)
    {
			if (DynamicMapDrawPhase != esriDynamicMapDrawPhase.esriDMDPDynamicLayers)
				return;

      // initialize symbology for dynamic drawing
      if (m_bOnce)
      {
        // create the glyphs for the bike position as well as for the route
        IDynamicGlyphFactory2 dynamicGlyphFactory = dynamicDisplay.DynamicGlyphFactory as IDynamicGlyphFactory2;
        IColor whiteTransparentColor = (IColor)ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255));

        Bitmap bitmap = new Bitmap(GetType(), "Icons.bicycle-icon.bmp");
        m_bikeGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), false, whiteTransparentColor);

        bitmap = new Bitmap(GetType(), "Icons.cat.bmp");
        m_catGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), false, whiteTransparentColor);

        bitmap = new Bitmap(GetType(), "Icons.gps.png");
        m_gpsGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), false, whiteTransparentColor);

        ISymbol routeSymbol = CreateBikeRouteSymbol();
        m_bikeRouteGlyph = dynamicGlyphFactory.CreateDynamicGlyph(routeSymbol);

        // create the heart rate glyphs series
        CreateHeartRateAnimationGlyphs(dynamicGlyphFactory);

        // get the default internal text glyph
        m_textGlyph = dynamicGlyphFactory.get_DynamicGlyph(1, esriDynamicGlyphType.esriDGlyphText, 1);

        // do one time casting
        m_dynamicSymbolProperties = dynamicDisplay as IDynamicSymbolProperties2;
        m_dynamicCompoundMarker = dynamicDisplay as IDynamicCompoundMarker2;
        m_dynamicScreenDisplay = dynamicDisplay as IDynamicScreenDisplay;

        m_bOnce = false;
      }

      // draw the trail
      m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolLine, m_bikeRouteGlyph);
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolLine, 1.0f, 1.0f, 1.0f, 1.0f);
      m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolLine, 1.0f, 1.0f);
      m_dynamicSymbolProperties.LineContinuePattern = true;
      dynamicDisplay.DrawPolyline(m_bikeRouteGeometry);

      if (m_playbackFormat == GPSPlaybackFormat.HST)
      {
        // adjust the bike lap additional info point to draw at the top left corner of the window
        m_additionalInfoPoint.Y = Display.DisplayTransformation.get_DeviceFrame().bottom - 70;

        // draw additional lap information
        DrawLapInfo(dynamicDisplay);

        // draw the heart-rate and altitude
        DrawHeartRateAnimation(dynamicDisplay, m_gpsPosition);

				// draw the current position as a marker glyph
				m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_bikeGlyph);
				m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f);
				m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.2f, 1.2f);
				m_dynamicSymbolProperties.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRANorth);
				m_dynamicSymbolProperties.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, (float)(m_heading - 90));
				dynamicDisplay.DrawMarker(m_gpsPosition);
      }
      else
      {
				DrawGPSInfo(dynamicDisplay, m_gpsPosition);
      }

      

    }

    void OnDynamicMapStarted(IDisplay Display, IDynamicDisplay dynamicDisplay)
    {
      lock (m_bikePositionInfo)
      {
        // update the bike position
        if (m_bikePositionInfo.positionCount != m_bikePositionCount)
        {
          // update the geometry
          m_gpsPosition.PutCoords(m_bikePositionInfo.position.X, m_bikePositionInfo.position.Y);

          // check if needed to update the map extent
          if (m_bTrackMode)
          {
            IEnvelope mapExtent = m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.FittedBounds;
            mapExtent.CenterAt(m_gpsPosition);
            m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.VisibleBounds = mapExtent;
          }

          // update the bike trail
          m_wksPoints[0] = m_bikePositionInfo.position;
          m_geometryBridge.AddWKSPoints(m_bikeRouteGeometry, ref m_wksPoints);

          // get the GPS altitude reading
          m_altitudeString = string.Format("Altitude: {0} m", m_bikePositionInfo.altitudeMeters.ToString("####.#"));

          if (m_playbackFormat == GPSPlaybackFormat.HST)
          {
            // calculate the heading
            m_heading = Math.Atan2(m_bikePositionInfo.position.X - m_wksPrevPosition.X, m_bikePositionInfo.position.Y - m_wksPrevPosition.Y);
            m_heading *= (180 / Math.PI);
            if (m_heading < 0)
              m_heading += 360;

            m_heartRateString = string.Format("{0} BPM", m_bikePositionInfo.heartRate);
          }
          else
          {
            m_heading = m_bikePositionInfo.course;
            m_speed = string.Format("Speed: {0} MPH", m_bikePositionInfo.speed.ToString("###.#"));
          }

          m_wksPrevPosition.X = m_bikePositionInfo.position.X;
          m_wksPrevPosition.Y = m_bikePositionInfo.position.Y;
          m_bikePositionCount = m_bikePositionInfo.positionCount;
        }
      }

      // explicitly call refresh in order to make the dynamic display fire AfterDynamicDraw event 
      m_hookHelper.ActiveView.Refresh();
    }

    private void XmlReaderTask(object data)
    {
      bool bFirst = true;
      DateTime nextTime = DateTime.Now;
      DateTime currentTime = DateTime.Now;
      double lat = 0;
      double lon = 0;
      double altitude = 0;
      double course = 0;
      double speed = 0;
      int heartRate = 0;
      XmlNode trackPoint = null;
      XmlNode nextTrackPointTimeNode = null;

      XmlDocTaksData taskData = (XmlDocTaksData)data;

      XmlDocument bikeDataDoc = new XmlDocument();
      XmlTextReader xmlTextReader = new XmlTextReader(m_xmlPath);
      bikeDataDoc.Load(xmlTextReader);

      XmlElement rootElement = bikeDataDoc.DocumentElement;

      if (m_playbackFormat == GPSPlaybackFormat.HST)
      {
        XmlNodeList laps = rootElement.GetElementsByTagName("Lap");
        foreach (XmlNode lap in laps)
        {
          // get lap average and maximum heart rate
          XmlNode averageHeartRate = ((XmlElement)lap).GetElementsByTagName("AverageHeartRateBpm")[0];
          XmlNode maximumHeartRate = ((XmlElement)lap).GetElementsByTagName("MaximumHeartRateBpm")[0];
          XmlNode calories = ((XmlElement)lap).GetElementsByTagName("Calories")[0];
          XmlNode maxSpeed = ((XmlElement)lap).GetElementsByTagName("MaximumSpeed")[0];
          XmlNode distance = ((XmlElement)lap).GetElementsByTagName("DistanceMeters")[0];

          // update the position info
          lock (m_bikePositionInfo)
          {
            m_bikePositionInfo.lapCount++;
            m_bikePositionInfo.lapAverageHeartRate = Convert.ToInt32(averageHeartRate.InnerText);
            m_bikePositionInfo.lapMaximumHeartRate = Convert.ToInt32(maximumHeartRate.InnerText);
            m_bikePositionInfo.lapCalories = Convert.ToInt32(calories.InnerText);
            m_bikePositionInfo.lapMaximumSpeed = Convert.ToDouble(maxSpeed.InnerText);
            m_bikePositionInfo.lapDistanceMeters = Convert.ToDouble(distance.InnerText);
          }

          XmlNodeList tracks = ((XmlElement)lap).GetElementsByTagName("Track");
          foreach (XmlNode track in tracks)
          {
            XmlNodeList trackpoints = ((XmlElement)track).GetElementsByTagName("Trackpoint");
            // read each time one track point ahead in order to calculate the lag time between 
            // the current track point and the next one. This time will be used as the wait time
            // for the AutoResetEvent.
            foreach (XmlNode nextTrackPoint in trackpoints)
            {
              bool bNextTime = false;
              bool bTime = false;
              bool bPosition = false;
              bool bAltitude = false;
              bool bHeartRate = false;

              // if this is the first node in the first track make it current
              if (bFirst)
              {
                trackPoint = nextTrackPoint;
                bFirst = false;
                continue;
              }

              // get the time from the next point in order to calculate the lag time
              nextTrackPointTimeNode = ((XmlElement)nextTrackPoint).GetElementsByTagName("Time")[0];
              if (nextTrackPointTimeNode == null)
                continue;
              else
              {
                nextTime = Convert.ToDateTime(nextTrackPointTimeNode.InnerText);
                bNextTime = true;
              }

              if (trackPoint.ChildNodes.Count == 4)
              {
                foreach (XmlNode trackPointNode in trackPoint.ChildNodes)
                {

                  if (trackPointNode.Name == "Time")
                  {
                    currentTime = Convert.ToDateTime(trackPointNode.InnerText);
                    bTime = true;
                  }
                  else if (trackPointNode.Name == "Position")
                  {
                    XmlNode latNode = trackPointNode["LatitudeDegrees"];
                    lat = Convert.ToDouble(latNode.InnerText);

                    XmlNode lonNode = trackPointNode["LongitudeDegrees"];
                    lon = Convert.ToDouble(lonNode.InnerText);

                    bPosition = true;
                  }
                  else if (trackPointNode.Name == "AltitudeMeters")
                  {
                    altitude = Convert.ToDouble(trackPointNode.InnerText);
                    bAltitude = true;
                  }
                  else if (trackPointNode.Name == "HeartRateBpm")
                  {
                    heartRate = Convert.ToInt32(trackPointNode.InnerText);
                    bHeartRate = true;
                  }
                }

                if (bNextTime && bTime && bPosition && bAltitude && bHeartRate)
                {

                  TimeSpan ts = nextTime - currentTime;

                  lock (m_bikePositionInfo)
                  {
                    m_bikePositionInfo.position.X = lon;
                    m_bikePositionInfo.position.Y = lat;
                    m_bikePositionInfo.altitudeMeters = altitude;
                    m_bikePositionInfo.heartRate = heartRate;
                    m_bikePositionInfo.time = currentTime;
                    m_bikePositionInfo.positionCount++;
                  }

                  // wait for the duration of the time span or bail out if the thread was signaled
                  if (m_autoEvent.WaitOne((int)(ts.TotalMilliseconds / m_playbackSpeed), true))
                  {
                    return;
                  }
                }
              }

              // make the next track point current
              trackPoint = nextTrackPoint;
            }
          }
        }
      }
      else // GPX
      {
        XmlNodeList trackpoints = bikeDataDoc.DocumentElement.GetElementsByTagName("trkpt");

        // read each time one track point ahead in order to calculate the lag time between 
        // the current track point and the next one. This time will be used as the wait time
        // for the AutoResetEvent.
        foreach (XmlNode nextTrackPoint in trackpoints)
        {
          // if this is the first node in the first track make it current
          if (bFirst)
          {
            trackPoint = nextTrackPoint;
            bFirst = false;
            continue;
          }

          // get the time from the next point in order to calculate the lag time
          nextTrackPointTimeNode = ((XmlElement)nextTrackPoint).GetElementsByTagName("time")[0];
          if (nextTrackPointTimeNode == null)
            continue;
          else
          {
            nextTime = Convert.ToDateTime(nextTrackPointTimeNode.InnerText);
          }

          lat = Convert.ToDouble(trackPoint.Attributes["lat"].InnerText);
          lon = Convert.ToDouble(trackPoint.Attributes["lon"].InnerText);

          foreach (XmlNode trackPointNode in trackPoint.ChildNodes)
          {
            if (trackPointNode.Name == "time")
            {
              currentTime = Convert.ToDateTime(trackPointNode.InnerText);
            }
            else if (trackPointNode.Name == "ele")
            {
              altitude = Convert.ToDouble(trackPointNode.InnerText);
            }
            else if (trackPointNode.Name == "course")
            {
              course = Convert.ToDouble(trackPointNode.InnerText);
            }
            else if (trackPointNode.Name == "speed")
            {
              speed = Convert.ToDouble(trackPointNode.InnerText);
            }
          }          

          TimeSpan ts = nextTime - currentTime;

          lock (m_bikePositionInfo)
          {
            m_bikePositionInfo.position.X = lon;
            m_bikePositionInfo.position.Y = lat;
            m_bikePositionInfo.altitudeMeters = altitude;
            m_bikePositionInfo.time = currentTime;
            m_bikePositionInfo.course = course;
            m_bikePositionInfo.speed = speed;
            m_bikePositionInfo.positionCount++;
          }

          // wait for the duration of the time span or bail out if the thread was signaled
          if (m_autoEvent.WaitOne((int)(ts.TotalMilliseconds / m_playbackSpeed), true))
          {
            return;
          }

          // make the next track point current
          trackPoint = nextTrackPoint;
        }
      }

      // close the reader when done
      xmlTextReader.Close();
    }

    private ISymbol CreateBikeRouteSymbol()
    {
        IColor color = (IColor)ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(0, 90, 250));
      ICharacterMarkerSymbol charMarkerSymbol = new CharacterMarkerSymbolClass();
      charMarkerSymbol.Color = color;
      charMarkerSymbol.Font = ESRI.ArcGIS.ADF.Connection.Local.Converter.ToStdFont(new Font("ESRI Default Marker", 17.0f, FontStyle.Bold));
      charMarkerSymbol.CharacterIndex = 189;
      charMarkerSymbol.Size = 17;

      IMarkerLineSymbol markerLineSymbol = new MarkerLineSymbolClass();
      markerLineSymbol.Color = color;
      markerLineSymbol.Width = 17.0;
      markerLineSymbol.MarkerSymbol = (IMarkerSymbol)charMarkerSymbol;

      // Makes a new Cartographic Line symbol and sets its properties
      ICartographicLineSymbol cartographicLineSymbol = markerLineSymbol as ICartographicLineSymbol;

      // In order to set additional properties like offsets and dash patterns we must create an ILineProperties object
      ILineProperties lineProperties = cartographicLineSymbol as ILineProperties;
      lineProperties.Offset = 0;

      // Here's how to do a template for the pattern of marks and gaps
      double[] hpe = new double[4];
      hpe[0] = 0;
      hpe[1] = 39;
      hpe[2] = 1;
      hpe[3] = 0;

      ITemplate template = new TemplateClass();
      template.Interval = 1;
      for (int i = 0; i < hpe.Length; i = i + 2)
      {
        template.AddPatternElement(hpe[i], hpe[i + 1]);
      }
      lineProperties.Template = template;

      // Set the basic and cartographic line properties
      cartographicLineSymbol.Color = color;

      color = (IColor)ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(0, 220, 100));

      // create a simple line
      ISimpleLineSymbol simpleLineSymbol = new SimpleLineSymbolClass();
      simpleLineSymbol.Color = color;
      simpleLineSymbol.Style = esriSimpleLineStyle.esriSLSSolid;
      simpleLineSymbol.Width = 1.2;

      IMultiLayerLineSymbol multiLayerLineSymbol = new MultiLayerLineSymbolClass();
      multiLayerLineSymbol.AddLayer((ILineSymbol)cartographicLineSymbol);
      multiLayerLineSymbol.AddLayer((ILineSymbol)simpleLineSymbol);

      return multiLayerLineSymbol as ISymbol;
    }

    private void CreateHeartRateAnimationGlyphs(IDynamicGlyphFactory2 dynamicGlyphFactory)
    {
        IColor whiteTransparentColor = (IColor)ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255));

      m_heartRateGlyph = new IDynamicGlyph[5];
      string heartRateIcon;
      string heartIconBaseName = "Icons.valentine-heart";
      Bitmap bitmap = null;
      int imagesize = 16;
      for (int i = 0; i < 5; i++)
      {
        heartRateIcon = heartIconBaseName + imagesize + ".bmp";
        bitmap = new Bitmap(GetType(), heartRateIcon);
        m_heartRateGlyph[i] = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), false, whiteTransparentColor);
        m_heartRateGlyph[i].SetAnchor(20.0f, -40.0f);
        imagesize += 2;
      }
    }

    private void DrawHeartRateAnimation(IDynamicDisplay dynamicDisplay, IPoint bikePoint)
    {
      m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_heartRateGlyph[m_heartRateCounter]);
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f);
      m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f);
      m_dynamicSymbolProperties.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRAScreen);
      m_dynamicSymbolProperties.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, 0.0f);
      dynamicDisplay.DrawMarker(bikePoint);

      m_textGlyph.SetAnchor(-35.0f, -50.0f);
      m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolText, m_textGlyph);
      m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = true;
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0f, 0.8f, 0.0f, 1.0f);
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolFill, 0.0f, 0.0f, 0.0f, 1.0f);
      m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHALeft;
      dynamicDisplay.DrawText(bikePoint, m_heartRateString);

      m_textGlyph.SetAnchor(-20.0f, -30.0f);
      m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolText, m_textGlyph);
      dynamicDisplay.DrawText(bikePoint, m_altitudeString);

      if (m_drawCycles % 5 == 0)
      {
        m_heartRateCounter++;

        if (m_heartRateCounter > 4)
          m_heartRateCounter = 0;
      }

      m_drawCycles++;
      if (m_drawCycles == 5)
        m_drawCycles = 0;
    }

		private void DrawGPSInfo(IDynamicDisplay dynamicDisplay, IPoint gpsPosition)
		{

			// altitude is already available
			string course;
			string speed;

			lock (m_bikePositionInfo)
			{
				course = string.Format("Course {0} DEG", m_bikePositionInfo.course.ToString("###.##"));
				speed = string.Format("Speed {0} MPH", m_bikePositionInfo.speed.ToString("###.##"));
			}

			string gpsInfo = string.Format("{0}\n{1}\n{2}", course, speed, m_altitudeString);

		  m_textGlyph.SetAnchor(-35.0f, -47.0f);
			m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolText, m_textGlyph);
			m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = true;
			m_dynamicSymbolProperties.set_Heading(esriDynamicSymbolType.esriDSymbolText, 0.0f);
			m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0f, 0.8f, 0.0f, 1.0f);
			m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolText, 1.0f, 1.0f);
			m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolFill, 0.0f, 0.0f, 0.0f, 0.6f);
			m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHALeft;
			dynamicDisplay.DrawText(m_gpsPosition, gpsInfo);


			m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_gpsGlyph);
			m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f);
			m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, m_gpsSymbolScale, m_gpsSymbolScale);
			m_dynamicSymbolProperties.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRANorth);
			m_dynamicSymbolProperties.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, (float)(m_heading - 90));
			dynamicDisplay.DrawMarker(m_gpsPosition);

			if (m_drawCycles % 5 == 0)
			{
				// increment the symbol size
				m_gpsSymbolScale += 0.05f;

				if (m_gpsSymbolScale > 1.2f)
					m_gpsSymbolScale = 0.8f;
			}

			m_drawCycles++;
			if (m_drawCycles == 5)
				m_drawCycles = 0;
		}

    private void DrawLapInfo(IDynamicDisplay dynamicDisplay)
    {
      string lapCount;
      string lapInfo;
      string lapHeartRateInfo;

      lock (m_bikePositionInfo)
      {
        lapCount = string.Format("Lap #{0}", m_bikePositionInfo.lapCount);
        lapInfo = string.Format("Lap information:\nDistance: {0}m\nMaximum speed - {1}\nCalories - {2}", m_bikePositionInfo.lapDistanceMeters.ToString("#####.#"), m_bikePositionInfo.lapMaximumSpeed.ToString("###.#"), m_bikePositionInfo.lapCalories);
        lapHeartRateInfo = string.Format("Lap heart rate info:\nAverage - {0}\nMaximum - {1}", m_bikePositionInfo.lapAverageHeartRate, m_bikePositionInfo.lapMaximumHeartRate);
      }

      m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker, m_catGlyph);
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f);
      m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f);
      m_dynamicSymbolProperties.set_RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker, esriDynamicSymbolRotationAlignment.esriDSRAScreen);
      m_dynamicSymbolProperties.set_Heading(esriDynamicSymbolType.esriDSymbolMarker, 0.0f);

      m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = true;
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0f, 0.8f, 0.0f, 1.0f);
      m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolFill, 0.0f, 0.0f, 0.0f, 1.0f);
      m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHALeft;
      m_textGlyph.SetAnchor(0.0f, 0.0f);
      m_dynamicSymbolProperties.set_DynamicGlyph(esriDynamicSymbolType.esriDSymbolText, m_textGlyph);
			string[] strLapInfo = new string[] { lapCount, lapInfo, lapHeartRateInfo };
			m_dynamicCompoundMarker.DrawScreenArrayMarker(m_additionalInfoPoint, ref nullString, ref nullString, ref strLapInfo, ref nullString, ref nullString);

    }

    private string GetPlaybackXmlPath()
    {
      OpenFileDialog ofd = new OpenFileDialog();
      ofd.CheckFileExists = true;
      ofd.Multiselect = false;
			ofd.Filter = "HST files (*.hst)|*.hst|GPX files (*.gpx)|*.gpx|XML files (*.xml)|*.xml";
      ofd.RestoreDirectory = true;
      ofd.Title = "Input biking log file";

      DialogResult result = ofd.ShowDialog();
      if (result == DialogResult.OK)
      {
        string fileExtension = System.IO.Path.GetExtension(ofd.FileName).ToUpper();
        if (fileExtension == ".GPX")
          m_playbackFormat = GPSPlaybackFormat.GPX;
        else if (fileExtension == ".HST")
          m_playbackFormat = GPSPlaybackFormat.HST;
        else if (fileExtension == ".XML")
          m_playbackFormat = GPSPlaybackFormat.XML;

        return ofd.FileName;
      }

      return string.Empty;
    }
    #endregion
  }
}
[Visual Basic .NET]

DynamicBikingCmd.vb

Imports Microsoft.VisualBasic
Imports System
Imports System.IO
Imports System.Drawing
Imports System.Xml
Imports System.Xml.XPath
Imports System.Threading
Imports System.Windows.Forms
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
Imports ESRI.ArcGIS.ADF.BaseClasses
Imports ESRI.ArcGIS.ADF.CATIDs
Imports ESRI.ArcGIS.Controls
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.esriSystem

  ''' <summary>
  ''' Summary description for DynamicBikingCmd.
  ''' </summary>
  <Guid("f01054d2-0130-4124-8436-1bf2942bf2b6"), ClassInterface(ClassInterfaceType.None), ProgId("DynamicBikingCmd")> _
  Public NotInheritable Class DynamicBikingCmd : Inherits BaseCommand
	#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)
	  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)
	  ControlsCommands.Unregister(regKey)

	End Sub

	#End Region
	#End Region

	#Region "class members"
	Private Enum GPSPlaybackFormat
	  HST = 0
	  GPX = 1
	  XML = 2
	End Enum
	Private m_playbackFormat As GPSPlaybackFormat = GPSPlaybackFormat.GPX
	Private m_hookHelper As IHookHelper
	Private m_dynamicMap As IDynamicMap = Nothing
	Private m_activeView As IActiveView = Nothing
	Private m_bConnected As Boolean = False

	Private m_gpsPosition As IPoint = Nothing
	Private m_additionalInfoPoint As IPoint = Nothing
	Private m_bikeRouteGeometry As IPointCollection4 = Nothing
	Private m_geometryBridge As IGeometryBridge2 = Nothing
	Private m_wksPoints As WKSPoint() = New WKSPoint(0){}
	Private m_wksPrevPosition As WKSPoint

	Private m_dynamicSymbolProperties As IDynamicSymbolProperties2 = Nothing
	Private m_dynamicCompoundMarker As IDynamicCompoundMarker2 = Nothing
	Private m_dynamicScreenDisplay As IDynamicScreenDisplay = Nothing
	Private m_bikeGlyph As IDynamicGlyph = Nothing
	Private m_bikeRouteGlyph As IDynamicGlyph = Nothing
	Private m_textGlyph As IDynamicGlyph = Nothing
	Private m_catGlyph As IDynamicGlyph = Nothing
	Private m_gpsGlyph As IDynamicGlyph = Nothing
	Private m_heartRateGlyph As IDynamicGlyph()
	Private m_gpsSymbolScale As Single = 1.0f
	Private m_heading As Double = 0
	Private m_heartRateString As String = String.Empty
	Private m_altitudeString As String = String.Empty
	Private m_speed As String = String.Empty
	Private m_bOnce As Boolean = True
	Private m_heartRateCounter As Integer = 0
	Private m_drawCycles As Integer = 0
	Public m_playbackSpeed As Integer = 10
	Private m_bTrackMode As Boolean = False
	Private nullString As String() = Nothing

	Private m_xmlPath As String = String.Empty
	' xml loader thread stuff
	Private m_dataLoaderThread As Thread = Nothing
	Private Shared m_autoEvent As AutoResetEvent = New AutoResetEvent(False)
	Private m_bikePositionCount As Integer = 0

	Private NotInheritable Class BikePositionInfo
	  Public Sub New()

	  End Sub

	  Public position As WKSPoint
	  Public time As DateTime
	  Public altitudeMeters As Double
	  Public heartRate As Integer
	  Public lapCount As Integer
	  Public lapAverageHeartRate As Integer
	  Public lapMaximumHeartRate As Integer
	  Public lapCalories As Integer
	  Public lapMaximumSpeed As Double
	  Public lapDistanceMeters As Double
	  Public course As Double
	  Public speed As Double
	  Public positionCount As Integer
	End Class

	Private m_bikePositionInfo As BikePositionInfo = Nothing

	Private Structure XmlDocTaksData
	  Public xmlDocPath As String
	End Structure

	#End Region

	#Region "class constructor"
	Public Sub New()
	  MyBase.m_category = ".NET Samples"
	  MyBase.m_caption = "Dynamic Biking"
	  MyBase.m_message = "Dynamic Biking"
	  MyBase.m_toolTip = "Dynamic Biking"
	  MyBase.m_name = "DynamicBikingCmd"

	  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

	Protected Overrides Sub Finalize()
	  If Not m_dataLoaderThread Is Nothing AndAlso m_dataLoaderThread.ThreadState = ThreadState.Running Then
			m_autoEvent.Set()
			m_dataLoaderThread.Join()
	  End If
	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)
        If hook Is Nothing Then
            Return
        End If

        If m_hookHelper Is Nothing Then
            m_hookHelper = New HookHelperClass()
        End If

        m_hookHelper.Hook = hook

        m_activeView = m_hookHelper.ActiveView

        m_geometryBridge = New GeometryEnvironmentClass()

        m_wksPrevPosition.X = 0
        m_wksPrevPosition.Y = 0
    End Sub

    ''' <summary>
    ''' Occurs when this command is clicked
    ''' </summary>
    Public Overrides Sub OnClick()
        m_dynamicMap = TryCast(m_hookHelper.FocusMap, IDynamicMap)
        If m_dynamicMap Is Nothing Then
            Return
        End If

        If (Not m_dynamicMap.DynamicMapEnabled) Then
            MessageBox.Show("Please enable dynamic mode and try again.")
            Return
        End If

        If (Not m_bConnected) Then
            m_xmlPath = GetPlaybackXmlPath()
            If m_xmlPath = String.Empty Then
                Return
            End If

            m_bikePositionInfo = New BikePositionInfo()
            m_bikePositionInfo.positionCount = m_bikePositionCount
            m_bikePositionInfo.altitudeMeters = 0
            m_bikePositionInfo.time = DateTime.Now
            m_bikePositionInfo.position.X = 0
            m_bikePositionInfo.position.Y = 0
            m_bikePositionInfo.heartRate = 0
            m_bikePositionInfo.lapCount = 0
            m_bikePositionInfo.lapAverageHeartRate = 0
            m_bikePositionInfo.lapMaximumHeartRate = 0
            m_bikePositionInfo.lapDistanceMeters = 0
            m_bikePositionInfo.lapMaximumSpeed = 0
            m_bikePositionInfo.lapCalories = 0

            m_gpsPosition = New PointClass()
            m_additionalInfoPoint = New PointClass()
            m_additionalInfoPoint.PutCoords(70, 90)
            m_bikeRouteGeometry = New PolylineClass()

            ' wire dynamic map events
            AddHandler (CType(m_dynamicMap, IDynamicMapEvents_Event)).AfterDynamicDraw, AddressOf OnAfterDynamicDraw
            AddHandler (CType(m_dynamicMap, IDynamicMapEvents_Event)).DynamicMapStarted, AddressOf OnDynamicMapStarted

            ' spin the thread that plays the data from the xml file
            m_dataLoaderThread = New Thread(New ParameterizedThreadStart(AddressOf XmlReaderTask))

            Dim taskData As XmlDocTaksData
            taskData.xmlDocPath = m_xmlPath
            m_dataLoaderThread.Start(taskData)
        Else
            ' unwire wire dynamic map events
            RemoveHandler (CType(m_dynamicMap, IDynamicMapEvents_Event)).AfterDynamicDraw, AddressOf OnAfterDynamicDraw
            RemoveHandler (CType(m_dynamicMap, IDynamicMapEvents_Event)).DynamicMapStarted, AddressOf OnDynamicMapStarted

            ' force the bike xml playback thread to quite
            m_autoEvent.Set()
            m_dataLoaderThread.Join()

            System.Diagnostics.Trace.WriteLine("Done!!!")
        End If

        m_bConnected = Not m_bConnected
    End Sub

    Public Overrides ReadOnly Property Checked() As Boolean
        Get
            Return m_bConnected
        End Get
    End Property

#End Region

	#Region "public properties"
	Public ReadOnly Property IsPlaying() As Boolean
	  Get
		  Return m_bConnected
	  End Get
	End Property

	Public Property TrackMode() As Boolean
	  Get
		  Return m_bTrackMode
	  End Get
	  Set
		  m_bTrackMode = Value
	  End Set
	End Property

	Public Property PlaybackSpeed() As Integer
	  Get
		  Return m_playbackSpeed
	  End Get
	  Set
		  m_playbackSpeed = Value
	  End Set
	End Property
	#End Region

	#Region "Private methods"
	Private Sub OnAfterDynamicDraw(ByVal DynamicMapDrawPhase As esriDynamicMapDrawPhase, ByVal Display As IDisplay, ByVal dynamicDisplay As IDynamicDisplay)
	  
		if (DynamicMapDrawPhase <> esriDynamicMapDrawPhase.esriDMDPDynamicLayers) then
			return
		End if

		' initialize symbology for dynamic drawing
	  If m_bOnce Then
			' create the glyphs for the bike position as well as for the route
			Dim dynamicGlyphFactory As IDynamicGlyphFactory2 = TryCast(dynamicDisplay.DynamicGlyphFactory, IDynamicGlyphFactory2)
            Dim whiteTransparentColor As IColor = CType(ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255)), IColor)

			Dim bitmap As Bitmap = New Bitmap(Me.GetType(), "bicycle-icon.bmp")
			m_bikeGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), False, whiteTransparentColor)

			bitmap = New Bitmap(Me.GetType(), "cat.bmp")
			m_catGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), False, whiteTransparentColor)

			bitmap = New Bitmap(Me.GetType(), "gps.png")
			m_gpsGlyph = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), False, whiteTransparentColor)

			Dim routeSymbol As ISymbol = CreateBikeRouteSymbol()
			m_bikeRouteGlyph = dynamicGlyphFactory.CreateDynamicGlyph(routeSymbol)

			' create the heart rate glyphs series
			CreateHeartRateAnimationGlyphs(dynamicGlyphFactory)

			' get the default internal text glyph
			m_textGlyph = dynamicGlyphFactory.DynamicGlyph(1, esriDynamicGlyphType.esriDGlyphText, 1)

			' do one time casting
			m_dynamicSymbolProperties = TryCast(dynamicDisplay, IDynamicSymbolProperties2)
			m_dynamicCompoundMarker = TryCast(dynamicDisplay, IDynamicCompoundMarker2)
			m_dynamicScreenDisplay = TryCast(dynamicDisplay, IDynamicScreenDisplay)

			m_bOnce = False
	  End If

	  ' draw the trail
	  m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolLine)= m_bikeRouteGlyph
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolLine, 1.0f, 1.0f, 1.0f, 1.0f)
	  m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolLine, 1.0f, 1.0f)
	  m_dynamicSymbolProperties.LineContinuePattern = True
	  dynamicDisplay.DrawPolyline(m_bikeRouteGeometry)

	  If m_playbackFormat = GPSPlaybackFormat.HST Then
			' adjust the bike lap additional info point to draw at the top left corner of the window
			m_additionalInfoPoint.Y = Display.DisplayTransformation.DeviceFrame().bottom - 70

            ' draw additional lap information
			DrawLapInfo(dynamicDisplay)

			' draw the heart-rate and altitude
			DrawHeartRateAnimation(dynamicDisplay, m_gpsPosition)

			' draw the current position as a marker glyph
			m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker) = m_bikeGlyph
			m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0F, 1.0F, 1.0F, 1.0F)
			m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.2F, 1.2F)
			m_dynamicSymbolProperties.RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker) = esriDynamicSymbolRotationAlignment.esriDSRANorth
			m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolMarker) = CSng(m_heading - 90)
			dynamicDisplay.DrawMarker(m_gpsPosition)
	  Else
				DrawGPSInfo(dynamicDisplay, m_gpsPosition)
	  End If

	End Sub

	Private Sub OnDynamicMapStarted(ByVal Display As IDisplay, ByVal dynamicDisplay As IDynamicDisplay)
	  SyncLock m_bikePositionInfo
		' update the bike position
		If m_bikePositionInfo.positionCount <> m_bikePositionCount Then
		  ' update the geometry
		  m_gpsPosition.PutCoords(m_bikePositionInfo.position.X, m_bikePositionInfo.position.Y)

		  ' check if needed to update the map extent
		  If m_bTrackMode Then
				Dim mapExtent As IEnvelope = m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.FittedBounds
				mapExtent.CenterAt(m_gpsPosition)
				m_hookHelper.ActiveView.ScreenDisplay.DisplayTransformation.VisibleBounds = mapExtent
		  End If

		  ' update the bike trail
		  m_wksPoints(0) = m_bikePositionInfo.position
		  m_geometryBridge.AddWKSPoints(m_bikeRouteGeometry, m_wksPoints)

		  ' get the GPS altitude reading
		  m_altitudeString = String.Format("Altitude: {0} m", m_bikePositionInfo.altitudeMeters.ToString("####.#"))

		  If m_playbackFormat = GPSPlaybackFormat.HST Then
				' calculate the heading
				m_heading = Math.Atan2(m_bikePositionInfo.position.X - m_wksPrevPosition.X, m_bikePositionInfo.position.Y - m_wksPrevPosition.Y)
				m_heading *= (180 / Math.PI)
				If m_heading < 0 Then
					m_heading += 360
				End If

				m_heartRateString = String.Format("{0} BPM", m_bikePositionInfo.heartRate)
		  Else
				m_heading = m_bikePositionInfo.course
				m_speed = String.Format("Speed: {0} MPH", m_bikePositionInfo.speed.ToString("###.#"))
		  End If

		  m_wksPrevPosition.X = m_bikePositionInfo.position.X
		  m_wksPrevPosition.Y = m_bikePositionInfo.position.Y
		  m_bikePositionCount = m_bikePositionInfo.positionCount
		End If
	  End SyncLock

	  ' explicitly call refresh in order to make the dynamic display fire AfterDynamicDraw event 
	  m_hookHelper.ActiveView.Refresh()
	End Sub

	Private Sub XmlReaderTask(ByVal data As Object)
	  Dim bFirst As Boolean = True
	  Dim nextTime As DateTime = DateTime.Now
	  Dim currentTime As DateTime = DateTime.Now
	  Dim lat As Double = 0
	  Dim lon As Double = 0
	  Dim altitude As Double = 0
	  Dim course As Double = 0
	  Dim speed As Double = 0
	  Dim heartRate As Integer = 0
	  Dim trackPoint As XmlNode = Nothing
	  Dim nextTrackPointTimeNode As XmlNode = Nothing

	  Dim taskData As XmlDocTaksData = CType(data, XmlDocTaksData)

	  Dim bikeDataDoc As XmlDocument = New XmlDocument()
	  Dim xmlTextReader As XmlTextReader = New XmlTextReader(m_xmlPath)
	  bikeDataDoc.Load(xmlTextReader)

	  Dim rootElement As XmlElement = bikeDataDoc.DocumentElement

	  If m_playbackFormat = GPSPlaybackFormat.HST Then
			Dim laps As XmlNodeList = rootElement.GetElementsByTagName("Lap")
			For Each lap As XmlNode In laps
				' get lap average and maximum heart rate
				Dim averageHeartRate As XmlNode = (CType(lap, XmlElement)).GetElementsByTagName("AverageHeartRateBpm")(0)
				Dim maximumHeartRate As XmlNode = (CType(lap, XmlElement)).GetElementsByTagName("MaximumHeartRateBpm")(0)
				Dim calories As XmlNode = (CType(lap, XmlElement)).GetElementsByTagName("Calories")(0)
				Dim maxSpeed As XmlNode = (CType(lap, XmlElement)).GetElementsByTagName("MaximumSpeed")(0)
				Dim distance As XmlNode = (CType(lap, XmlElement)).GetElementsByTagName("DistanceMeters")(0)

				' update the position info
				SyncLock m_bikePositionInfo
					m_bikePositionInfo.lapCount += 1
					m_bikePositionInfo.lapAverageHeartRate = Convert.ToInt32(averageHeartRate.InnerText)
					m_bikePositionInfo.lapMaximumHeartRate = Convert.ToInt32(maximumHeartRate.InnerText)
					m_bikePositionInfo.lapCalories = Convert.ToInt32(calories.InnerText)
					m_bikePositionInfo.lapMaximumSpeed = Convert.ToDouble(maxSpeed.InnerText)
					m_bikePositionInfo.lapDistanceMeters = Convert.ToDouble(distance.InnerText)
				End SyncLock

				Dim tracks As XmlNodeList = (CType(lap, XmlElement)).GetElementsByTagName("Track")
				For Each track As XmlNode In tracks
					Dim trackpoints As XmlNodeList = (CType(track, XmlElement)).GetElementsByTagName("Trackpoint")
                    ' read each time one track point ahead in order to calculate the lag time between 
                    ' the current track point and the next one. This time will be used as the wait time
					' for the AutoResetEvent.
					For Each nextTrackPoint As XmlNode In trackpoints
						Dim bNextTime As Boolean = False
						Dim bTime As Boolean = False
						Dim bPosition As Boolean = False
						Dim bAltitude As Boolean = False
						Dim bHeartRate As Boolean = False

						' if this is the first node in the first track make it current
						If bFirst Then
							trackPoint = nextTrackPoint
							bFirst = False
							Continue For
						End If

						' get the time from the next point in order to calculate the lag time
						nextTrackPointTimeNode = (CType(nextTrackPoint, XmlElement)).GetElementsByTagName("Time")(0)
						If nextTrackPointTimeNode Is Nothing Then
							Continue For
						Else
							nextTime = Convert.ToDateTime(nextTrackPointTimeNode.InnerText)
							bNextTime = True
						End If

						If trackPoint.ChildNodes.Count = 4 Then
							For Each trackPointNode As XmlNode In trackPoint.ChildNodes

								If trackPointNode.Name = "Time" Then
									currentTime = Convert.ToDateTime(trackPointNode.InnerText)
									bTime = True
								ElseIf trackPointNode.Name = "Position" Then
									Dim latNode As XmlNode = trackPointNode("LatitudeDegrees")
									lat = Convert.ToDouble(latNode.InnerText)

									Dim lonNode As XmlNode = trackPointNode("LongitudeDegrees")
									lon = Convert.ToDouble(lonNode.InnerText)

									bPosition = True
								ElseIf trackPointNode.Name = "AltitudeMeters" Then
									altitude = Convert.ToDouble(trackPointNode.InnerText)
									bAltitude = True
								ElseIf trackPointNode.Name = "HeartRateBpm" Then
									heartRate = Convert.ToInt32(trackPointNode.InnerText)
									bHeartRate = True
								End If
							Next trackPointNode

							If bNextTime AndAlso bTime AndAlso bPosition AndAlso bAltitude AndAlso bHeartRate Then

								Dim ts As TimeSpan = nextTime.Subtract(currentTime)

								SyncLock m_bikePositionInfo
									m_bikePositionInfo.position.X = lon
									m_bikePositionInfo.position.Y = lat
									m_bikePositionInfo.altitudeMeters = altitude
									m_bikePositionInfo.heartRate = heartRate
									m_bikePositionInfo.time = currentTime
									m_bikePositionInfo.positionCount += 1
								End SyncLock

								' wait for the duration of the time span or bail out if the thread was signaled
								If m_autoEvent.WaitOne(CInt(ts.TotalMilliseconds / m_playbackSpeed), True) Then
									Return
								End If
							End If
						End If

						' make the next track point current
						trackPoint = nextTrackPoint
					Next nextTrackPoint
				Next track
			Next lap
	  Else ' GPX
			Dim trackpoints As XmlNodeList = bikeDataDoc.DocumentElement.GetElementsByTagName("trkpt")

            ' read each time one track point ahead in order to calculate the lag time between 
            ' the current track point and the next one. This time will be used as the wait time
			' for the AutoResetEvent.
			For Each nextTrackPoint As XmlNode In trackpoints
				' if this is the first node in the first track make it current
				If bFirst Then
					trackPoint = nextTrackPoint
					bFirst = False
					Continue For
				End If

				' get the time from the next point in order to calculate the lag time
				nextTrackPointTimeNode = (CType(nextTrackPoint, XmlElement)).GetElementsByTagName("time")(0)
				If nextTrackPointTimeNode Is Nothing Then
					Continue For
				Else
					nextTime = Convert.ToDateTime(nextTrackPointTimeNode.InnerText)
				End If

				lat = Convert.ToDouble(trackPoint.Attributes("lat").InnerText)
				lon = Convert.ToDouble(trackPoint.Attributes("lon").InnerText)

				For Each trackPointNode As XmlNode In trackPoint.ChildNodes
					If trackPointNode.Name = "time" Then
						currentTime = Convert.ToDateTime(trackPointNode.InnerText)
					ElseIf trackPointNode.Name = "ele" Then
						altitude = Convert.ToDouble(trackPointNode.InnerText)
					ElseIf trackPointNode.Name = "course" Then
						course = Convert.ToDouble(trackPointNode.InnerText)
					ElseIf trackPointNode.Name = "speed" Then
						speed = Convert.ToDouble(trackPointNode.InnerText)
					End If
				Next trackPointNode

				Dim ts As TimeSpan = nextTime.Subtract(currentTime)

				SyncLock m_bikePositionInfo
					m_bikePositionInfo.position.X = lon
					m_bikePositionInfo.position.Y = lat
					m_bikePositionInfo.altitudeMeters = altitude
					m_bikePositionInfo.time = currentTime
					m_bikePositionInfo.course = course
					m_bikePositionInfo.speed = speed
					m_bikePositionInfo.positionCount += 1
				End SyncLock

				' wait for the duration of the time span or bail out if the thread was signaled
				If m_autoEvent.WaitOne(CInt(ts.TotalMilliseconds / m_playbackSpeed), True) Then
					Return
				End If

				' make the next track point current
				trackPoint = nextTrackPoint
			Next nextTrackPoint
	  End If

	  ' close the reader when done
	  xmlTextReader.Close()
	End Sub

	Private Function CreateBikeRouteSymbol() As ISymbol
        Dim newColor As IColor = CType(ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(0, 90, 250)), IColor)
	  Dim charMarkerSymbol As ICharacterMarkerSymbol = New CharacterMarkerSymbolClass()
	  charMarkerSymbol.Color = newColor
        charMarkerSymbol.Font = ESRI.ArcGIS.ADF.Connection.Local.Converter.ToStdFont(New Font("ESRI Default Marker", 17.0F, FontStyle.Bold))
	  charMarkerSymbol.CharacterIndex = 189
	  charMarkerSymbol.Size = 17

	  Dim markerLineSymbol As IMarkerLineSymbol = New MarkerLineSymbolClass()
	  markerLineSymbol.Color = newColor
	  markerLineSymbol.Width = 17.0
	  markerLineSymbol.MarkerSymbol = CType(charMarkerSymbol, IMarkerSymbol)

	  ' Makes a new Cartographic Line symbol and sets its properties
	  Dim cartographicLineSymbol As ICartographicLineSymbol = TryCast(markerLineSymbol, ICartographicLineSymbol)

	  ' In order to set additional properties like offsets and dash patterns we must create an ILineProperties object
	  Dim lineProperties As ILineProperties = TryCast(cartographicLineSymbol, ILineProperties)
	  lineProperties.Offset = 0

	  ' Here's how to do a template for the pattern of marks and gaps
	  Dim hpe As Double() = New Double(3){}
	  hpe(0) = 0
	  hpe(1) = 39
	  hpe(2) = 1
	  hpe(3) = 0

	  Dim template As ITemplate = New TemplateClass()
	  template.Interval = 1
	  Dim i As Integer = 0
	  Do While i < hpe.Length
			template.AddPatternElement(hpe(i), hpe(i + 1))
		  i = i + 2
	  Loop
	  lineProperties.Template = template

	  ' Set the basic and cartographic line properties
	  cartographicLineSymbol.Color = newColor

        newColor = CType(ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(0, 220, 100)), IColor)

	  ' create a simple line
	  Dim simpleLineSymbol As ISimpleLineSymbol = New SimpleLineSymbolClass()
	  simpleLineSymbol.Color = newColor
	  simpleLineSymbol.Style = esriSimpleLineStyle.esriSLSSolid
	  simpleLineSymbol.Width = 1.2

	  Dim multiLayerLineSymbol As IMultiLayerLineSymbol = New MultiLayerLineSymbolClass()
	  multiLayerLineSymbol.AddLayer(CType(cartographicLineSymbol, ILineSymbol))
	  multiLayerLineSymbol.AddLayer(CType(simpleLineSymbol, ILineSymbol))

	  Return TryCast(multiLayerLineSymbol, ISymbol)
	End Function

	Private Sub CreateHeartRateAnimationGlyphs(ByVal dynamicGlyphFactory As IDynamicGlyphFactory2)
        Dim whiteTransparentColor As IColor = CType(ESRI.ArcGIS.ADF.Connection.Local.Converter.ToRGBColor(Color.FromArgb(255, 255, 255)), IColor)

	  m_heartRateGlyph = New IDynamicGlyph(4){}
	  Dim heartRateIcon As String
	  Dim heartIconBaseName As String = "valentine-heart"
	  Dim bitmap As Bitmap = Nothing
	  Dim imagesize As Integer = 16
	  For i As Integer = 0 To 4
			heartRateIcon = heartIconBaseName & imagesize & ".bmp"
			bitmap = New Bitmap(Me.GetType(), heartRateIcon)
			m_heartRateGlyph(i) = dynamicGlyphFactory.CreateDynamicGlyphFromBitmap(esriDynamicGlyphType.esriDGlyphMarker, bitmap.GetHbitmap().ToInt32(), False, whiteTransparentColor)
			m_heartRateGlyph(i).SetAnchor(20.0F, -40.0F)
			imagesize += 2
	  Next i
	End Sub

	Private Sub DrawHeartRateAnimation(ByVal dynamicDisplay As IDynamicDisplay, ByVal bikePoint As IPoint)
	  m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker)= m_heartRateGlyph(m_heartRateCounter)
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f)
	  m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f)
	  m_dynamicSymbolProperties.RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker)= esriDynamicSymbolRotationAlignment.esriDSRAScreen
	  m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolMarker)= 0.0f
	  dynamicDisplay.DrawMarker(bikePoint)

	  m_textGlyph.SetAnchor(-35.0f, -50.0f)
	  m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolText)= m_textGlyph
	  m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = True
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0f, 0.8f, 0.0f, 1.0f)
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolFill, 0.0f, 0.0f, 0.0f, 1.0f)
	  m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHALeft
	  dynamicDisplay.DrawText(bikePoint, m_heartRateString)

	  m_textGlyph.SetAnchor(-20.0f, -30.0f)
	  m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolText)= m_textGlyph
	  dynamicDisplay.DrawText(bikePoint, m_altitudeString)

	  If m_drawCycles Mod 5 = 0 Then
			m_heartRateCounter += 1

			If m_heartRateCounter > 4 Then
				m_heartRateCounter = 0
			End If
	  End If

	  m_drawCycles += 1
	  If m_drawCycles = 5 Then
			m_drawCycles = 0
	  End If
	End Sub

	Private Sub DrawGPSInfo(ByVal dynamicDisplay As IDynamicDisplay, ByVal gpsPosition As IPoint)

		' altitude is already available
		Dim course As String
		Dim speed As String

		SyncLock m_bikePositionInfo
			course = String.Format("Course {0} DEG", m_bikePositionInfo.course.ToString("###.##"))
			speed = String.Format("Speed {0} MPH", m_bikePositionInfo.speed.ToString("###.##"))
		End SyncLock

		Dim gpsInfo As String = String.Format("{0}" & Constants.vbLf & "{1}" & Constants.vbLf & "{2}", course, speed, m_altitudeString)

		m_textGlyph.SetAnchor(-35.0F, -47.0F)
		m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolText) = m_textGlyph
		m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = True
		m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolText) = 0.0F
		m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0F, 0.8F, 0.0F, 1.0F)
		m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolText, 1.0F, 1.0F)
		m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolFill, 0.0F, 0.0F, 0.0F, 0.6F)
		m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHALeft
		dynamicDisplay.DrawText(m_gpsPosition, gpsInfo)

		m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker) = m_gpsGlyph
		m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0F, 1.0F, 1.0F, 1.0F)
		m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, m_gpsSymbolScale, m_gpsSymbolScale)
		m_dynamicSymbolProperties.RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker) = esriDynamicSymbolRotationAlignment.esriDSRANorth
		m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolMarker) = CSng(m_heading - 90)
		dynamicDisplay.DrawMarker(m_gpsPosition)

		If m_drawCycles Mod 5 = 0 Then
			' increment the symbol size
			m_gpsSymbolScale += 0.05F

			If m_gpsSymbolScale > 1.2F Then
				m_gpsSymbolScale = 0.8F
			End If
		End If

		m_drawCycles += 1
		If m_drawCycles = 5 Then
			m_drawCycles = 0
		End If
	End Sub

	Private Sub DrawLapInfo(ByVal dynamicDisplay As IDynamicDisplay)
	  Dim lapCount As String
	  Dim lapInfo As String
	  Dim lapHeartRateInfo As String

	  SyncLock m_bikePositionInfo
		lapCount = String.Format("Lap #{0}", m_bikePositionInfo.lapCount)
		lapInfo = String.Format("Lap information:" & Constants.vbLf & "Distance: {0}m" & Constants.vbLf & "Maximum speed - {1}" & Constants.vbLf & "Calories - {2}", m_bikePositionInfo.lapDistanceMeters.ToString("#####.#"), m_bikePositionInfo.lapMaximumSpeed.ToString("###.#"), m_bikePositionInfo.lapCalories)
		lapHeartRateInfo = String.Format("Lap heart rate info:" & Constants.vbLf & "Average - {0}" & Constants.vbLf & "Maximum - {1}", m_bikePositionInfo.lapAverageHeartRate, m_bikePositionInfo.lapMaximumHeartRate)
	  End SyncLock

	  m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolMarker)= m_catGlyph
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f, 1.0f, 1.0f)
	  m_dynamicSymbolProperties.SetScale(esriDynamicSymbolType.esriDSymbolMarker, 1.0f, 1.0f)
	  m_dynamicSymbolProperties.RotationAlignment(esriDynamicSymbolType.esriDSymbolMarker)= esriDynamicSymbolRotationAlignment.esriDSRAScreen
	  m_dynamicSymbolProperties.Heading(esriDynamicSymbolType.esriDSymbolMarker)= 0.0f

	  m_dynamicSymbolProperties.TextBoxUseDynamicFillSymbol = True
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolText, 0.0f, 0.8f, 0.0f, 1.0f)
	  m_dynamicSymbolProperties.SetColor(esriDynamicSymbolType.esriDSymbolFill, 0.0f, 0.0f, 0.0f, 1.0f)
	  m_dynamicSymbolProperties.TextBoxHorizontalAlignment = esriTextHorizontalAlignment.esriTHALeft
	  m_textGlyph.SetAnchor(0.0f, 0.0f)
	  m_dynamicSymbolProperties.DynamicGlyph(esriDynamicSymbolType.esriDSymbolText)= m_textGlyph
		
		Dim strLapInfo As String() = New String() { lapCount, lapInfo, lapHeartRateInfo }
		m_dynamicCompoundMarker.DrawScreenArrayMarker(m_additionalInfoPoint, nullString, nullString, strLapInfo, nullString, nullString)

	End Sub

	Private Function GetPlaybackXmlPath() As String
	  Dim ofd As OpenFileDialog = New OpenFileDialog()
	  ofd.CheckFileExists = True
	  ofd.Multiselect = False
			ofd.Filter = "HST files (*.hst)|*.hst|GPX files (*.gpx)|*.gpx|XML files (*.xml)|*.xml"
	  ofd.RestoreDirectory = True
	  ofd.Title = "Input biking log file"

	  Dim result As DialogResult = ofd.ShowDialog()
	  If result = System.Windows.Forms.DialogResult.OK Then
			Dim fileExtension As String = System.IO.Path.GetExtension(ofd.FileName).ToUpper()
			If fileExtension = ".GPX" Then
				m_playbackFormat = GPSPlaybackFormat.GPX
			ElseIf fileExtension = ".HST" Then
				m_playbackFormat = GPSPlaybackFormat.HST
			ElseIf fileExtension = ".XML" Then
				m_playbackFormat = GPSPlaybackFormat.XML
			End If

			Return ofd.FileName
	  End If
		Return String.Empty

	End Function
	#End Region
End Class