Common Custom data source
Common_CustomDataSource_CSharp\REXMLDataSource_CSharp\MapFunctionality.cs
// Copyright 2011 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.
// 


namespace REXMLDataSource_CSharp
{
    // Along with IGISDataSource and IGISResource, IGISFunctionality is one of the three required
    // interfaces for any Web ADF Data Source implementation.  This interface is responsible for
    // providing members to functionally interact with the underlying data.  Essentially, an 
    // IGISFunctionality implementation can be thought of as describing what can be done with the 
    // data.
    //
    // This particular implementation inherits from IMapFunctionality, which implements 
    // IGISFunctionality.  IMapFunctionality provides methods and properties that are the foundation
    // of map operations like zoom and pan.
    public class MapFunctionality : ESRI.ArcGIS.ADF.Web.DataSources.IMapFunctionality
    {
        #region Instance Variables

        private ESRI.ArcGIS.ADF.Web.DisplaySettings m_displaySettings = null;
        private System.Web.UI.WebControls.WebControl m_webControl = null;
        private bool m_maintainsState = false;
        private ESRI.ArcGIS.ADF.Web.SpatialReference.SpatialReference m_spatialReference = null;
        private string m_name = string.Empty;
        private ESRI.ArcGIS.ADF.Web.DataSources.IGISResource m_gisResource = null;
        bool m_initialized = false;
        private ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet m_graphicsDataSet = null;

        #endregion

        #region Constructor

        // Constructor that initializes functionality name and the resource with which the
        // functionality is associated
        public MapFunctionality(string name, REXMLDataSource_CSharp.MapResource resource)
        {
            m_name = name;
            m_gisResource = resource;
        }

        #endregion

        #region REXML MapFunctionality Members

        // Allows access to the MapResource object associated with the functionality
        public REXMLDataSource_CSharp.MapResource MapResource
        {
            get
            {
                return m_gisResource as REXMLDataSource_CSharp.MapResource;
            }
        }

        // Key for storing and retrieving object state to and from the underlying data source's state table
        private string FunctionalityStateKey
        {
            get
            {
                string szResource = m_gisResource.GetType().ToString() + ":" + m_gisResource.Name;
                string szThis = this.GetType().ToString() + ":" + m_name;
                return (szResource + "," + szThis);
            }
        }

        // Allows read access to the GraphicsDataSet containing the REXML data
        public ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsDataSet GraphicsDataSet
        {
            get
            {
                // Check whether the functionality maintains state
                if (!m_maintainsState)
                {
                    // Get a reference to the MapResource associated with the functionality
                    REXMLDataSource_CSharp.MapResource mapResource = m_gisResource as
                        REXMLDataSource_CSharp.MapResource;
                    // If the MapResource's GraphicsDataSet is null, return null.  Otherwise,
                    // return the dataset.
                    return mapResource == null ? null : mapResource.Graphics;
                }
                else
                {
                    // check whether the functionality instance's graphics dataset is null
                    if (m_graphicsDataSet != null)
                    {
                        // return the instance graphics dataset
                        return m_graphicsDataSet;
                    }
                    else
                    {
                        // Get a reference to the MapResource associated with the functionality
                        REXMLDataSource_CSharp.MapResource mapResource = m_gisResource as
                            REXMLDataSource_CSharp.MapResource;

                        // Make sure neither the MapResource nor the MapResource's graphics dataset
                        // are null
                        if (mapResource != null && mapResource.Graphics != null)
                        {
                            // Initialize the instance's graphics dataset as a clone of the associated
                            // MapResource's
                            m_graphicsDataSet = mapResource.Graphics.Clone();
                        }

                        // return the functionality instance's graphics dataset
                        return m_graphicsDataSet;
                    }
                }
            }
        }

        // Returns the extent of the Web ADF Map Control associated with the functionality
        public ESRI.ArcGIS.ADF.Web.Geometry.Envelope GetMapExtent()
        {
            // Attempt to get a reference to the Map Control associated with the instance by casting
            // the instance's WebControl member
            ESRI.ArcGIS.ADF.Web.UI.WebControls.Map adfMap = m_webControl as
                ESRI.ArcGIS.ADF.Web.UI.WebControls.Map;

            // If a reference was successfully established, return the contents of the control's Extent
            // property.  Otherwise, return null.
            if (adfMap == null) return null;
            else return adfMap.Extent;
        }

        // Returns the screen width and height in pixels of the Web ADF Map Control associated
        // with the functionality
        public void GetMapDimensions(out int width, out int height)
        {
            width = -1;
            height = -1;

            // Attempt to get a reference to the Map Control associated with the instance by casting
            // the instance's WebControl member
            ESRI.ArcGIS.ADF.Web.UI.WebControls.Map adfMap = m_webControl as
                ESRI.ArcGIS.ADF.Web.UI.WebControls.Map;

            // Make sure a reference to the Map Control was successfully established
            if (adfMap == null) return;

            // Make sure the Map Control has a valid TilingScheme for the REXML resource
            if (adfMap.GetTilingScheme(m_gisResource.Name) == null) return;

            // Set the width and height output parameters
            width = adfMap.GetTilingScheme(m_gisResource.Name).ViewWidth;
            height = adfMap.GetTilingScheme(m_gisResource.Name).ViewHeight;
        }

        // Allows internal retrieval by layer ID of a layer referenced by the functionality
        private ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer getLayer(string layerID)
        {
            // Cast the table in the functionality's graphics dataset at the passed-in layer ID
            // to a Web ADF GraphicsLayer and return
            ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer adfGraphicsLayer =
              (ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer)m_graphicsDataSet.Tables[layerID];
            return adfGraphicsLayer;
        }

        #endregion

        #region IMapFunctionality Members

        #region IMapFunctionality Properties

        // Allows read/write access to the flag indicating whether the functionality maintains state
        public bool MaintainsState
        {
            get
            { return m_maintainsState; }
            set
            { m_maintainsState = value; }
        }

        // Allows read/write access to the DisplaySettings of the MapResource instance associated with
        // the functionality
        public ESRI.ArcGIS.ADF.Web.DisplaySettings DisplaySettings
        {
            get
            {
                // Get a reference to the functionality's associated MapResource
                REXMLDataSource_CSharp.MapResource rexmlMapResource = m_gisResource as
                    REXMLDataSource_CSharp.MapResource;

                // Check the flag indicating whether the functionality maintains state.  If not, return
                // the associated MapResource's DisplaySettings
                if (m_maintainsState)
                {
                    // Check whether the functionality instance's DisplaySettings object has been initialized
                    if (m_displaySettings == null)
                    {
                        // Set the instance's DisplaySettings object to a clone of the associated
                        // MapResource's DisplaySettings
                        m_displaySettings =
                            (ESRI.ArcGIS.ADF.Web.DisplaySettings)rexmlMapResource.DisplaySettings.Clone();
                    }
                    // Return the functionality instance's DisplaySettings
                    return m_displaySettings;
                }
                else return rexmlMapResource.DisplaySettings;
            }
            set
            {
                // Get a reference to the functionality's associated MapResource
                REXMLDataSource_CSharp.MapResource rexmlMapResource = m_gisResource as
                    REXMLDataSource_CSharp.MapResource;

                // Check whether the functionality maintains state.  If so, set the functionality's
                // DisplaySettings object to the passed-in value.  If not, set the associated MapResource's
                // DisplaySettings object to the passed-in value.
                if (m_maintainsState) m_displaySettings = value;
                else rexmlMapResource.DisplaySettings = value;
            }
        }

        // Not implemented here, but required to be stubbed out for implementations of IMapFunctionality.
        public ESRI.ArcGIS.ADF.Web.DataSources.Units Units
        {
            get { throw new System.NotImplementedException(); }
        }

        // Allows read access to the IDs of the layers referenced by the functionality's graphics dataset
        public object[] LayerIDs
        {
            get
            {
                // Make sure neither the functionality's graphics dataset nor the graphics dataset's
                // tables collection are null
                if (m_graphicsDataSet == null || m_graphicsDataSet.Tables == null) return null;

                // Dimension the layer ID array with the number of tables in the functionality's 
                // graphics dataset
                object[] layerIDs = new object[m_graphicsDataSet.Tables.Count];

                // Iterate through the tables in the functionality's graphics dataset, adding the
                // name of each to the layer ID array
                for (int i = 0; i < m_graphicsDataSet.Tables.Count; i++)
                {
                    layerIDs[i] = m_graphicsDataSet.Tables[i].TableName;
                }

                return layerIDs;
            }
        }

        // Allows read/write access to the functionality's spatial reference
        public ESRI.ArcGIS.ADF.Web.SpatialReference.SpatialReference SpatialReference
        {
            get
            { return m_spatialReference; }
            set
            { m_spatialReference = value; }
        }

        // This implementation does not support rotation, but the property must be stubbed out
        // in order to implement IMapFunctionality
        public double Rotation
        {
            get
            { return double.NaN; }
        }
        #endregion

        #region IMapFunctionality Methods

        // Applies the ImageDescriptor of the functionality's display settings to its graphics dataset
        public void ApplyStateToDataSourceObjects()
        {
            // Make sure the functionality's graphics dataset is not null
            if (m_graphicsDataSet != null)
            {
                // If the functionality's display settings object is not null, apply its ImageDescriptor
                // to the graphics dataset.  This is actually applying the ImageDescriptor to the
                // underlying data source, since the graphics dataset references that of the data source.
                if (m_displaySettings != null) m_graphicsDataSet.ImageDescriptor = 
                    m_displaySettings.ImageDescriptor;
            }
        }

        // Retrieves the ImageDescriptor of the functionality's graphic dataset and applies it to the
        // functionality's display settings
        public void GetStateFromDataSourceObjects()
        {
            // Make sure the instance's graphics dataset is not null
            if (m_graphicsDataSet != null)
            {
                // If the instance's display settings object is not null, set its ImageDescriptor to
                // that of the instance's graphics dataset.  This is actually retrieiving the 
                // ImageDescriptor from the underlying data source, since the graphics dataset references
                // that of the data source.
                if (m_displaySettings != null) m_displaySettings.ImageDescriptor = 
                    m_graphicsDataSet.ImageDescriptor;
            }
        }

        // Retrieves the names and IDs of the layers referenced by the functionality instance
        public void GetLayers(out string[] layerIDs, out string[] layerNames)
        {
            layerIDs = layerNames = null;
            
            // Make sure neither the instance's graphics dataset nor the graphics dataset's tables
            // collection are null
            if (m_graphicsDataSet == null || m_graphicsDataSet.Tables == null) return;
            
            // Dimension the layer ID and names arrays with the number of tables in the graphics
            // dataset's tables collection
            layerIDs = new string[m_graphicsDataSet.Tables.Count];
            layerNames = new string[m_graphicsDataSet.Tables.Count];

            // Iterate through the graphics dataset's tables collection, storing the name of each
            // in both the layer ID and names arrays.  Note that for graphics layers, the layer ID
            // is the same as the layer name.
            for (int i = 0; i < m_graphicsDataSet.Tables.Count; i++)
            {
                layerIDs[i] = m_graphicsDataSet.Tables[i].TableName;
                layerNames[i] = m_graphicsDataSet.Tables[i].TableName;
            }
            return;
        }

        // Not meaningfully implemented here, but stubbed out in order to implement IMapFunctionality
        public void GetVisibleScale(string layerid, out double minscale, out double maxscale)
        {
            // At a minimum, required for MapTips
            minscale = double.NaN;
            maxscale = double.NaN;
        }

        // Not implemented
        public double GetScale(ESRI.ArcGIS.ADF.Web.Geometry.Envelope extent, int mapWidth, int mapHeight)
        {
            throw new System.NotImplementedException();
        }

        // Not meaningfully implemented
        public System.Collections.Generic.Dictionary<string, string> GetCopyrightText()
        {
            return new System.Collections.Generic.Dictionary<string, string>();
        }

        // Returns a MapImage containing a map of the layers referenced by the functionality (in MIME
        // data or as a url) at the passed-in extent
        public ESRI.ArcGIS.ADF.Web.MapImage DrawExtent
            (ESRI.ArcGIS.ADF.Web.Geometry.Envelope extentToDraw)
        {
            // Make sure the functionality instance's graphics dataset is not null
            if (m_graphicsDataSet != null)
            {
                // Update the state of the underlying data source
                ApplyStateToDataSourceObjects();
                // Return the MapImage generated by the graphics dataset's DrawExtent method
                return m_graphicsDataSet.DrawExtent(extentToDraw);
            }
            else return null;
        }

        // Returns the visibility of the layer with the passed-in ID
        public bool GetLayerVisibility(string layerID)
        {
            return getLayer(layerID).Visible;
        }

        // Sets the visibility of the layer with the passed-in ID to the passed-in boolean
        public void SetLayerVisibility(string layerID, bool visible)
        {
            getLayer(layerID).Visible = visible;
        }

        #endregion

        #endregion

        #region IGISFunctionality Members

        #region IGISFunctionality Properties

        // Allows read/write access to the functionality instance's name
        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        // Allows read/write access to the GISResource associated with the functionality instance
        public ESRI.ArcGIS.ADF.Web.DataSources.IGISResource Resource
        {
            get { return m_gisResource; }
            set { m_gisResource = value; }
        }

        // Allows read access to the instance member indicating whether the functionality has been initialized
        public bool Initialized
        {
            get { return m_initialized; }
        }

        // Allows retrieving or setting the WebControl associated with the functionality instance
        public System.Web.UI.WebControls.WebControl WebControl
        {
            get { return m_webControl; }
            set { m_webControl = value; }
        }

        #endregion

        #region IGISFunctionality Methods

        // Loads the state of the functionality object from the data source's state table
        public void LoadState()
        {
            // make sure references to the functionality's associated GISResource, the GISResource's
            // underlying GISDataSource, and the GISDataSource's state table are all valid
            if (m_gisResource == null || m_gisResource.DataSource == null || 
                m_gisResource.DataSource.State == null)
            {
                throw new System.Exception("The Resource associated with this functionality is not valid.");
            }

            // Some of the functionality instance's properties need to be explicitly stored in the state
            // table, while others do not.  An overview of these properties and the reasons for storing or
            // not storing them is as follows:

            // These properties are shared with and managed by the associated MapResource:
            // - Display Settings
            // - DataSource objects: GraphicsDataSet

            // These properties are shared with and managed by the associated DataSource objects:
            // - DisplaySettings.ImageDescriptor

            // These properties are dynamic and therefore do not need to be stored in state:
            // - LayerIDs
            // - Scale
            // - Rotation
            // - Extent

            // These properties are non-dynamic and exclusive to this instance, so they need to be 
            // stored in state:
            // - WebControl
            // - MaintainsState
            // - SpatialReference

            // Retrieve the functionality's state from the underlying data source's state table.  The
            // object state will be stored in the table at the index indicated by the private property
            // FunctionalityStateKey
            object functionalityState = m_gisResource.DataSource.State[FunctionalityStateKey];
            // Check whether the state was found
            if (functionalityState != null)
            {
                // Cast the state object to a REXML MapFunctionality
                REXMLDataSource_CSharp.MapFunctionality rexmlMapFunctionality = functionalityState as
                    REXMLDataSource_CSharp.MapFunctionality;
                
                // Get the properties stored in functionality state (as noted above) from stored state and
                // assign them to the appropriate instance variables
                m_maintainsState = rexmlMapFunctionality.MaintainsState;
                m_webControl = rexmlMapFunctionality.WebControl;
                m_spatialReference = rexmlMapFunctionality.SpatialReference;
                
                // If maintainsState, retrieve this functionality's own copies of the properties that are
                // shared with the associated MapResource instance and assign them to the appropriate
                // instance variables
                if (m_maintainsState)
                {
                    m_displaySettings = rexmlMapFunctionality.DisplaySettings;
                    m_graphicsDataSet = rexmlMapFunctionality.GraphicsDataSet;
                }
            }

            // Load the properties that are shared with the instance's associated DataSource objects. 
            // This will initialize the relevant instance variables with values from those objects.
            GetStateFromDataSourceObjects();

            // Any necessary logic for handling dynamic properties (listed above) would go here.
        }

        // Stores the functionality's state in the underlying data source's state table.
        public void SaveState()
        {
            // make sure references to the functionality's associated GISResource, the GISResource's
            // underlying GISDataSource, and the GISDataSource's state table are all valid
            if (m_gisResource == null) return;
            if (m_gisResource.DataSource == null) return;
            if (m_gisResource.DataSource.State == null) return;

            // Update the state of the underlying data source objects with relevant properties of the
            // functionality instance
            ApplyStateToDataSourceObjects();

            // Store the functionality instance in the data source's state table
            m_gisResource.DataSource.State[FunctionalityStateKey] = this;
        }

        // Set the flag indicating whether the functionality is intitialized to true.  Any necessary
        // start-up logic (i.e. instance variable initialization) should go here.
        public void Initialize()
        {
            m_graphicsDataSet = this.GraphicsDataSet;
            m_initialized = true;
        }

        // Set the flag indicating whether the functionality is intitialized to false.  Any necessary
        // disposal logic (e.g. releasing object references) should go here.  Note that, if there is
        // additional logic here, users of this class will have to EXPLCITLY call dispose.  It is not
        // invoked by other Web ADF components or the Page life-cycle.
        public void Dispose()
        {
            m_initialized = false;
        }

        // Retrieves boolean indicating whether the operation defined by the passed-in string is
        // supported by this implementation.  Here, only GetScale is explicitly not implemented.
        public bool Supports(string operation)
        {
            if (operation == "GetScale")
                return false;
            return true;
        }

        #endregion

        #endregion

    }
}