RSS weather 3D layer
GlobeCustomLayerBase.cs
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 

using System;
using System.Collections;
using System.Data;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.GlobeCore;


namespace RSSWeatherLayer3D
{

  /// <summary>
  /// An abstract base class for implementation of a custom globe layer
  /// </summary>
  public abstract class GlobeCustomLayerBase : Control,
                                               ILayer,
                                               IGeoDataset,
                                               ILayerGeneralProperties,
                                               IPersistVariant,
                                               IPersistStream,
                                               ILayerExtensions,
                                               ILayerInfo,
                                               ILegendInfo,
                                               ILayerDrawingProperties,
                                               ICustomGlobeLayer,
                                               IEnumerable,
                                               IDisposable
  {
    #region class members
    /// <summary>
    /// a win32 delete object  reference
    /// </summary>
    /// <param name="hObject"></param>
    /// <returns></returns>
    [DllImport("gdi32")]
    internal static extern int DeleteObject(IntPtr hObject);

    /// <summary>
    /// Keep the layer's extent. Returned by the ILayer::Extent property
    /// </summary>
    /// <remarks>The extent should be spatial-referenced to the DateFrame's spatial reference.
    /// </remarks>
    protected IEnvelope m_extent = null;

    /// <summary>
    /// Store the layer's underlying data spatial reference. Returned by IGeoDataset::SpatialReference.
    /// </summary>
    /// <remarks>This spatial reference should not be reprojected. In your inheriting 
    /// class you will need to have another parameter that will keep the DataFrame's spatial reference
    /// that would use to reproject the geometries and the extent of the layer.</remarks>
    protected ISpatialReference m_spRef = null;

    /// <summary>
    /// The legend group to store the legend class required by the TOC for the layer's renderer.
    /// Returned by ILegendGroup::get_LegendGroup property.
    /// </summary>
    protected ILegendGroup m_legendGroup = null;

    /// <summary>
    /// Layer's name. Returned by ILayer::Name property
    /// </summary>
    protected string m_sName = "GlobeCustomLayer";

    /// <summary>
    /// Flag which determines whether the layers is visible. Returned by ILayer::Visible
    /// </summary>
    /// <remarks>You should use this member in your inherited class in the Draw method.</remarks>
    protected bool m_bVisible = true;

    /// <summary>
    /// determines whether the layers is cached. Returned by ILayer::Cached.
    /// </summary>
    protected bool m_bCached = false;

    /// <summary>
    /// Flag which determines whether the layer is valid (connected to its data source, has valid information etc.).
    /// Returned by ILAyer::Valid.
    /// </summary>
    /// <remarks>You can use this flag to determine for example whether the layer can be available or not.</remarks>
    protected bool m_bValid = true;

    /// <summary>
    /// Indicates if the layer drawing properties are dirty.
    /// </summary>
    protected bool m_bDrawDirty = false;

    /// <summary>
    /// Keep the maximum scale value at which the layer will display
    /// </summary>
    protected double m_dblMaxScale;

    /// <summary>
    /// Keep the minimum scale value at which the layer will display
    /// </summary>
    protected double m_dblMinScale;

    /// <summary>
    /// determines whether the layers is supposed to show its MapTips
    /// </summary>
    protected bool m_ShowTips = false;

    /// <summary>
    /// The bitmap which represent the small image of the layer.
    /// </summary>
    /// <remarks>Shown in ArcCatalog or in the identify dialog</remarks>
    protected System.Drawing.Bitmap m_smallBitmap = null;

    /// <summary>
    /// The bitmap which represent the large image of the layer. 
    /// </summary>
    /// <remarks>This image will be shown in ArcCatalog when in Thumbnail view</remarks>
    protected System.Drawing.Bitmap m_largeBitmap = null;

    /// <summary>
    /// Handle for the small bitmap
    /// </summary>
    protected IntPtr m_hSmallBitmap = IntPtr.Zero;

    /// <summary>
    /// Handle for the large bitmap
    /// </summary>
    protected IntPtr m_hLargeBitmap = IntPtr.Zero;

    /// <summary>
    /// An arraylist to store the layer's extensions.
    /// </summary>
    protected ArrayList m_extensions = null;

    /// <summary>
    /// The data structure for the custom layer
    /// </summary>
    /// <remarks>By default, the DataTable gets initialized in the BaseClass Ctor with an ID column
    /// which is AutoIncremented. The BaseClass also support enumerator which is actually en enumerator of
    /// the table as well as a direct indexer.</remarks>
    protected DataTable m_table = null;
    #endregion

    #region constructor
    /// <summary>
    /// Class default constructor
    /// </summary>
    public GlobeCustomLayerBase()
    {
      m_sName = string.Empty;
      m_bVisible = true;
      m_bValid = true;
      m_bCached = false;
      m_dblMaxScale = 0;
      m_dblMinScale = 0;
      m_table = new DataTable("RECORDS");

      m_table.Columns.Add("ID", typeof(long));

      m_extensions = new ArrayList();

      ILegendClass legendClass = new LegendClassClass();
      legendClass.Label = "GlobeCustomLayer";

      m_legendGroup = new LegendGroupClass();
      m_legendGroup.Heading = "";
      m_legendGroup.Editable = false;
      m_legendGroup.AddClass(legendClass);

      m_sName = "GlobeCustomLayer";

      //call CreateControl in order to create the handle
      this.CreateControl();
    }
    #endregion

    #region IGeoDataset Members

    /// <summary>
    ///The layers geodataset extent which is a union of the extents of all
    /// the items of the layer
    /// </summary>
    /// <remarks>In your inheriting class, consider the following code to calculate the layer's extent:
    /// <code>
    /// public override IEnvelope Extent
    ///{
    ///  get
    ///  {
    ///    m_extent  = GetLayerExtent();
    ///    if (null == m_extent )
    ///      return null;
    ///
    ///    IEnvelope env = ((IClone)m_extent ).Clone() as IEnvelope;
    /// 
    ///    return env;
    ///  }
    ///}
    ///    private IEnvelope GetLayerExtent()
    ///{
    ///  if (null == base.m_spRef)
    ///  {
    ///    base.m_spRef = CreateGeographicSpatialReference();
    ///  }
    ///
    ///  IEnvelope env = new EnvelopeClass();
    ///  env.SpatialReference = base.m_spRef;
    ///  IPoint point = new PointClass();
    ///  point.SpatialReference = m_spRef;
    ///  foreach (DataRow r in m_table.Rows)
    ///  {
    ///    point.Y = Convert.ToDouble(r[3]);
    ///    point.X = Convert.ToDouble(r[4]);
    ///
    ///    env.Union(point.Envelope);
    ///  }
    /// 
    ///  return env;
    ///} 
    /// </code>
    /// </remarks>
    virtual public IEnvelope Extent
    {
      get
      {
        return m_extent;
      }
    }

    /// <summary>
    /// The spatial reference of the underlying data.
    /// </summary>
    /// <remarks>The property must return the underlying data spatial reference and
    /// must not reproject it into the layer's spatial reference </remarks>  
    virtual public ISpatialReference SpatialReference
    {
      get
      {
        return m_spRef;
      }
    }

    #endregion

    #region ILayerGeneralProperties Members

    /// <summary>
    /// Last maximum scale setting used by layer.
    /// </summary>
    virtual public double LastMaximumScale
    {
      get
      {
        return 0;
      }
    }

    /// <summary>
    /// Last minimum scale setting used by layer.
    /// </summary>
    virtual public double LastMinimumScale
    {
      get
      {
        return 0;
      }
    }

    /// <summary>
    /// Description for the layer.
    /// </summary>
    virtual public string LayerDescription
    {
      get
      {
        return null;
      }
      set
      {

      }
    }
    #endregion

    #region IPersistVariant Members

    /// <summary>
    /// The ID of the object.
    /// </summary>
    virtual public UID ID
    {
      get
      {
        // TODO:  Add clsCustomLayer.ID getter implementation
        return null;
      }
    }

    /// <summary>
    /// Loads the object properties from the stream.
    /// </summary>
    /// <param name="Stream"></param>
    /// <remarks>The Load method must read the data from the stream in the same order the data was 
    /// written to the stream in the Save method. 
    /// Streams are sequential; you must ensure that your data is saved and loaded in the correct order, 
    /// so that the correct data is written to the correct member.
    /// </remarks>
    virtual public void Load(IVariantStream Stream)
    {
      m_sName = (string)Stream.Read();
      m_bValid = (bool)Stream.Read();
      m_bCached = (bool)Stream.Read();
      m_dblMinScale = (double)Stream.Read();
      m_dblMaxScale = (double)Stream.Read();
      m_bDrawDirty = (bool)Stream.Read();

      m_spRef = (ISpatialReference)Stream.Read();
      m_extent = (IEnvelope)Stream.Read();

      m_extensions = (ArrayList)Stream.Read();
    }

    /// <summary>
    /// Saves the object properties to the stream.
    /// </summary>
    /// <param name="Stream"></param>
    virtual public void Save(IVariantStream Stream)
    {
      Stream.Write(m_sName);
      Stream.Write(m_bValid);
      Stream.Write(m_bCached);
      Stream.Write(m_dblMinScale);
      Stream.Write(m_dblMaxScale);
      Stream.Write(m_bDrawDirty);

      Stream.Write(m_spRef);
      Stream.Write(m_extent);

      Stream.Write(m_extensions);
    }

    #endregion

    #region IPersistStream Members
    /// <summary>
    /// Returns the class identifier (CLSID) for the component object.
    /// </summary>
    /// <param name="pClassID"></param>
    public virtual void GetClassID(out Guid pClassID)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Return the size in bytes of the stream needed to save the object.
    /// </summary>
    /// <param name="pcbSize"></param>
    public virtual void GetSizeMax(out _ULARGE_INTEGER pcbSize)
    {
      pcbSize = new _ULARGE_INTEGER();
    }

    /// <summary>
    /// Checks the object for changes since it was last saved.
    /// </summary>
    public virtual void IsDirty()
    {

    }

    /// <summary>
    /// Initializes an object from the stream where it was previously saved.
    /// </summary>
    /// <param name="pstm"></param>
    public virtual void Load(IStream pstm)
    {

    }

    /// <summary>
    /// Saves an object into the specified stream and indicates whether the object should reset its dirty flag.
    /// </summary>
    /// <param name="pstm"></param>
    /// <param name="fClearDirty"></param>
    public virtual void Save(IStream pstm, int fClearDirty)
    {

    }

    #endregion

    #region ILayer Members

    #region Properties

    /// <summary>
    /// Indicates if the layer shows map tips.
    /// </summary>
    /// <remarks>Indicates whether or not map tips are shown for the layer. 
    /// If set to True, then map tips will be shown for the layer. 
    /// You can determine the text that will be shown via TipText. 
    ///</remarks>
    virtual public bool ShowTips
    {
      get
      {
        return m_ShowTips;
      }
      set
      {
        m_ShowTips = value;
      }
    }

    /// <summary>
    /// The default area of interest for the layer. Returns the spatial-referenced extent of the layer.
    /// </summary>
    virtual public IEnvelope AreaOfInterest
    {
      get
      {
        return m_extent;
      }
    }

    /// <summary>
    /// Indicates if the layer is currently visible.
    /// </summary>
    virtual public new bool Visible
    {
      get
      {
        return m_bVisible;
      }
      set
      {
        m_bVisible = value;
      }
    }


    /// <summary>
    /// Indicates if the layer needs its own display cache.
    /// </summary>
    /// <remarks>This property indicates whether or not the layer requires its own display cache. 
    /// If this property is True, then the Map will use a separate display cache for the layer so 
    /// that it can be refreshed independently of other layers.</remarks>
    virtual public bool Cached
    {
      get
      {
        return m_bCached;
      }
      set
      {
        m_bCached = value;
      }
    }

    /// <summary>
    /// Minimum scale (representative fraction) at which the layer will display.
    /// </summary>
    /// <remarks>Specifies the minimum scale at which the layer will be displayed. 
    /// This means that if you zoom out beyond this scale, the layer will not display. 
    /// For example, specify 1000 to have the layer not display when zoomed out beyond 1:1000.</remarks>
    virtual public double MinimumScale
    {
      get
      {
        return m_dblMinScale;
      }
      set
      {
        m_dblMinScale = value;
      }
    }

    /// <summary>
    /// Indicates if the layer is currently valid.
    /// </summary>
    /// <remarks>The valid property indicates if the layer is currently valid.
    /// Layers that reference feature classes are valid when they hold a reference to a valid feature class.
    /// The property does not however validate the integrity of the feature classes reference to the database.
    /// Therefore, in rare situations if a datasource is removed after a layer is initialized, 
    /// the layer will report itself as valid but query attempts to the data source will error due to the lack 
    /// of underlying data.</remarks>
    virtual public bool Valid
    {
      get
      {
        return m_bValid;
      }
    }

    /// <summary>
    /// The Layer name.
    /// </summary>
    virtual public new string Name
    {
      get
      {
        return m_sName;
      }
      set
      {
        m_sName = value;
      }
    }

    /// <summary>
    /// Maximum scale (representative fraction) at which the layer will display.
    /// </summary>
    /// <remarks>Specifies the maximum scale at which the layer will be displayed. 
    /// This means that if you zoom in beyond this scale, the layer will not display. 
    /// For example, specify 500 to have the layer not display when zoomed in beyond 1:500.</remarks>
    virtual public double MaximumScale
    {
      get
      {
        return m_dblMaxScale;
      }
      set
      {
        m_dblMaxScale = value;
      }
    }

    /// <summary>
    /// Supported draw phases.
    /// </summary>
    /// <remarks>Indicates the draw phases supported by the layer (esriDPGeography, esriDPAnnotation, 
    /// esriDPSelection, or any combination of the three). 
    /// The supported draw phases are defined by esriDrawPhase. 
    /// When multiple draw phases are supported, the sum of the constants is used. 
    /// For example, if SupportedDrawPhases = 3 then the layer supports drawing in the geography and annotation phases.</remarks>
    public int SupportedDrawPhases
    {
      get
      {
        return (int)esriDrawPhase.esriDPGeography;
      }
    }

    /// <summary>
    /// Spatial reference for the layer.
    /// </summary>
    ///<remarks>This property is only used for map display, setting this property does not 
    ///change the spatial reference of the layer's underlying data. 
    ///The ArcGIS framework uses this property to pass the spatial reference from the map 
    ///to the layer in order to support on-the-fly projection.</remarks> 
    ISpatialReference ESRI.ArcGIS.Carto.ILayer.SpatialReference
    {
      set
      {
        m_spRef = value;
      }
    }

    #endregion

    #region Methods
    /// <summary>
    /// Map tip text at the specified location. 
    /// </summary>
    /// <param name="X"></param>
    /// <param name="Y"></param>
    /// <param name="Tolerance"></param>
    /// <returns>The text string that gets displayed as a map tip if ShowTips = true.</returns>
    virtual public string get_TipText(double X, double Y, double Tolerance)
    {
      return null;
    }

    /// <summary>
    /// Draws the layer to the specified display for the given draw phase.
    /// </summary>
    /// <param name="drawPhase"></param>
    /// <param name="Display"></param>
    /// <param name="trackCancel"></param>
    /// <remarks>This method draws the layer to the Display for the specified DrawPhase. 
    /// Use the TrackCancel object to allow the drawing of the layer to be interrupted by the user.</remarks>
    public virtual void Draw(esriDrawPhase drawPhase, IDisplay Display, ITrackCancel trackCancel)
    {

      return;
    }
    #endregion
    #endregion

    #region ILayerExtensions Members

    /// <summary>
    /// Removes the specified extension.
    /// </summary>
    /// <param name="Index"></param>
    public virtual void RemoveExtension(int Index)
    {
      if (Index < 0 || Index > m_extensions.Count - 1)
        return;

      m_extensions.RemoveAt(Index);
    }

    /// <summary>
    /// Number of extensions.
    /// </summary>
    public virtual int ExtensionCount
    {
      get
      {
        return m_extensions.Count;
      }
    }

    /// <summary>
    /// Adds a new extension.
    /// </summary>
    /// <param name="ext"></param>
    public virtual void AddExtension(object ext)
    {
      if (null == ext)
        return;

      m_extensions.Add(ext);
    }

    /// <summary>
    /// The extension at the specified index. 
    /// </summary>
    /// <param name="Index"></param>
    /// <returns></returns>
    public virtual object get_Extension(int Index)
    {
      if (Index < 0 || Index > m_extensions.Count - 1)
        return null;

      return m_extensions[Index];
    }

    #endregion

    #region ILayerInfo Members

    /// <summary>
    /// Small image that represents the layer.
    /// </summary>
    /// <remarks>The icon used to represent the layer in ArcCatalog's 'List' and 'Details' views.</remarks>
    public virtual int SmallImage
    {
      get
      {
        return m_hSmallBitmap.ToInt32();
      }
    }

    /// <summary>
    /// Large image that represents the layer when it is selected.
    /// </summary>
    /// <remarks>The icon used to represent the layer when it is selected in ArcCatalog's 'Large Icon' view.</remarks>
    public virtual int LargeSelectedImage
    {
      get
      {
        return m_hLargeBitmap.ToInt32();
      }
    }

    /// <summary>
    /// Small image that represents the layer when it is selected.
    /// </summary>
    /// <remarks>The icon used to represent the layer when it is selected in ArcCatalog's 'List' and 'Details' views.</remarks>
    public virtual int SmallSelectedImage
    {
      get
      {
        return m_hSmallBitmap.ToInt32();
      }
    }

    /// <summary>
    /// Large image that represents the layer.
    /// </summary>
    /// <remarks>The icon used to represent the layer in ArcCatalog's 'Large Icon' view.</remarks>
    public virtual int LargeImage
    {
      get
      {
        return m_hLargeBitmap.ToInt32();
      }
    }

    #endregion

    #region ILegendInfo Members

    /// <summary>
    /// Number of legend groups contained by the object.
    /// </summary>
    /// <remarks>The number of legend groups is determined by the implementation of the renderer, 
    /// consequently this property is read only. For example, SimpleRenderer has one group, 
    /// while a BiUniqueValueRenderer has any number of groups.</remarks>
    public virtual int LegendGroupCount
    {
      get { return 1; }
    }

    /// <summary>
    /// Optional. Defines legend formatting for layer rendered with this object.
    /// </summary>
    /// <remarks>Layer or renderer legend information is further formatted for display in ArcMap legends. 
    /// A renderer can override this formatting by returning a LegendItem for this property. 
    /// ESRI renderers typically do not return anything for this property. With this configuration, 
    /// legend formatting becomes a user or developer choice on the legend object.</remarks>
    public virtual ILegendItem LegendItem
    {
      get { return null; }
    }

    /// <summary>
    /// Indicates if symbols are graduated.
    /// </summary>
    /// <remarks>For example the proportional symbol renderer returns True for this property.
    /// You can use this property to distinguish between a layer symbolized with graduated color or 
    /// graduated symbol type layer symbology. Both of these symbolizations use a ClassBreaksRenderer, 
    /// but only a graduated symbol symbolization will return True for this property.</remarks>
    public virtual bool SymbolsAreGraduated
    {
      get { return false; }
      set { }
    }

    /// <summary>
    /// Legend group at the specified index.
    /// </summary>
    /// <param name="Index"></param>
    /// <returns></returns>
    /// <remarks>The content and number of legend groups is determined by the implementation of the renderer, 
    /// consequently this property is read only.</remarks>
    public virtual ILegendGroup get_LegendGroup(int Index)
    {
      return m_legendGroup;
    }

    #endregion

    #region ILayerDrawingProperties Members

    /// <summary>
    /// Indicates if the layer drawing properties are dirty.
    /// </summary>
    public virtual bool DrawingPropsDirty
    {
      get
      {
        return m_bDrawDirty;
      }
      set
      {
        m_bDrawDirty = value;
      }
    }

    #endregion

    #region ICustomGlobeLayer Members

    /// <summary>
    /// For custom OpenGL layers, perform immediate drawing.
    /// </summary>
    /// <param name="pGlobeViewer"></param>
    /// <remarks>This is where you should add your drawings on the Globe. This method must be overridden in 
    /// your inheriting class. DrawImmediate together with IGlobeDisplayEvents::BeforeDraw and  IGlobeDisplayEvents::AfterDraw
    /// is the only safe place where the OpenGL state is ready for custom actions.</remarks>
    public abstract void DrawImmediate(IGlobeViewer pGlobeViewer);

    /// <summary>
    /// The custom draw method.
    /// </summary>
    public virtual esriGlobeCustomDrawType DrawType
    {
      get { return esriGlobeCustomDrawType.esriGlobeCustomDrawOpenGL; }
    }

    /// <summary>
    /// Gets a rasterized data tile for the given globe tesselation coordinates.
    /// </summary>
    /// <param name="tilesize"></param>
    /// <param name="face"></param>
    /// <param name="level"></param>
    /// <param name="row"></param>
    /// <param name="col"></param>
    /// <param name="pData"></param>
    public virtual void GetTile(int tilesize, int face, int level, int row, int col, out byte[] pData)
    {
      pData = null;
    }

    /// <summary>
    /// The layer is hit by a picking operation.
    /// </summary>
    /// <param name="hitObjectID"></param>
    /// <param name="pHit3D"></param>
    public virtual void Hit(int hitObjectID, ESRI.ArcGIS.Analyst3D.IHit3D pHit3D)
    {
      return;
    }

    /// <summary>
    /// For rasterized types, defines highest resolution. Zero value indicates that globe-generated default value should be used.
    /// </summary>
    public virtual double MinimumCellSize
    {
      get { return 0; }
    }

    /// <summary>
    /// The symbol scale factor for the custom rasterized type.
    /// </summary>
    public virtual double SymbologyScalingFactor
    {
      get { return 0; }
    }

    /// <summary>
    /// The option to use Globe's disk caching.
    /// </summary>
    public virtual bool UseCache
    {
      get { return false; }
    }

    /// <summary>
    /// The option to use a local coordinate system origin for high precision drawing
    /// </summary>
    /// <remarks>Use this option (set to 'true') in case where you need to draw elements which
    /// are either small and close to the globe's surface.</remarks>
    public virtual bool HandlesLocalOrigin
    {
      get { return false; }
    }

    /// <summary>
    /// The local coordinate system origin for high precision drawing
    /// </summary>
    ///<remarks>In case of setting the HandlesLocalOrigin to 'true', The point of origin passed by the Globe.</remarks>
    public virtual WKSPointZ LocalOrigin
    {
      set { }
    }

    #endregion

    #region Data Structure basic functionality
    /// <summary>
    /// Create a new item (does not add it to the layer)
    /// </summary>
    /// <returns></returns>
    virtual public DataRow NewItem()
    {
      return m_table.NewRow();
    }

    /// <summary>
    /// Add an item to the layer
    /// </summary>
    /// <param name="row"></param>
    virtual public void AddItem(DataRow row)
    {
      m_table.Rows.Add(row);
    }

    /// <summary>
    /// Add an item to the Layer
    /// </summary>
    /// <param name="values"></param>
    virtual public void AddItem(object[] values)
    {
      m_table.Rows.Add(values);
    }

    /// <summary>
    /// Query for items in the layer
    /// </summary>
    /// <param name="queryFilter">WHERE clause</param>
    /// <returns></returns>
    virtual public DataRow[] Select(string queryFilter)
    {
      return m_table.Select(queryFilter);
    }

    /// <summary>
    /// Remove all items in the layer
    /// </summary>
    virtual public void Clear()
    {
      m_table.Rows.Clear();
    }

    /// <summary>
    /// Remove the item from the layer
    /// </summary>
    /// <param name="row">The row to remove</param>
    virtual public void RemoveItem(DataRow row)
    {
      m_table.Rows.Remove(row);
    }

    //indexer. Pass an index and return a record
    /// <summary>
    /// Indexer, returns the underlying DataRow for the given index
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public DataRow this[int index]
    {
      get
      {
        return m_table.Rows[index];
      }
    }

    /// <summary>
    /// return the number of geometries in the layer
    /// </summary>
    public int NumOfRecords
    {
      get
      {
        return m_table.Rows.Count;
      }
    }
    #endregion

    #region IEnumerable Members

    /// <summary>
    /// Allow users to directly enumerate through the layer's records
    /// </summary>
    /// <returns></returns>
    public IEnumerator GetEnumerator()
    {
      return m_table.Rows.GetEnumerator();
    }

    #endregion

    #region IDisposable Members

    /// <summary>
    /// Dispose the layer
    /// </summary>
    /// <returns></returns>
    public virtual new void Dispose()
    {
      if (IntPtr.Zero != m_hSmallBitmap)
        DeleteObject(m_hSmallBitmap);

      if (IntPtr.Zero != m_hLargeBitmap)
        DeleteObject(m_hLargeBitmap);

      m_extensions.Clear();
      m_table.Dispose();

      m_extent = null;
      m_spRef = null;
      m_legendGroup = null;

      GC.Collect();
      GC.WaitForPendingFinalizers();
    }

    #endregion

  }
}