Common_TaskResults_CSharp\ZoomToResults\ZoomToResults.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. // namespace ZoomToResults_CSharp { // To override ITaskResultsContainer methods implemented in TaskResults but not marked virtual, implement // ITaskResultsContainer methods directly. For example, the DisplayResults and StartTaskActivityIndicator // methods are implemented within this class. [System.Web.UI.ToolboxData(@"<{0}:ZoomToResults runat=""server"" Width=""200px"" Height=""200px"" BackColor=""#ffffff"" Font-Names=""Verdana"" Font-Size=""8pt"" ForeColor=""#000000""> </{0}:ZoomToResults>")] public class ZoomToResults : ESRI.ArcGIS.ADF.Web.UI.WebControls.TaskResults, ESRI.ArcGIS.ADF.Web.UI.WebControls.ITaskResultsContainer { #region Public Properties /// <summary> /// The maximum number of features the result set can contain for zooming to results to occur /// </summary> [System.ComponentModel.Category("TaskResults"), System.ComponentModel.DefaultValue(10), System.ComponentModel.Description( "The maximum number of features the result set can contain for zooming to results to occur"), System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute), System.ComponentModel.NotifyParentProperty(true)] public int MaxResultsForMapZoom { get { object o = StateManager.GetProperty("MaxResultsForMapZoom"); return (o == null) ? 10 : System.Convert.ToInt32(o); } set { if (value >= 0) { StateManager.SetProperty("MaxResultsForMapZoom", value); } else { throw new System.Exception(); } } } /// <summary> /// The maximum number of features the result set can contain for automatic result selection to occur /// </summary> [System.ComponentModel.Category("TaskResults"), System.ComponentModel.DefaultValue(10), System.ComponentModel.Description( "The maximum number of features the result set can contain for automatic result selection to occur"), System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute), System.ComponentModel.NotifyParentProperty(true)] public int MaxResultsForAutoSelect { get { object o = StateManager.GetProperty("MaxResultsForAutoSelect"); return (o == null) ? 10 : System.Convert.ToInt32(o); } set { if (value >= 0) { StateManager.SetProperty("MaxResultsForAutoSelect", value); } else { throw new System.Exception(); } } } /// <summary> /// Minimum width in map units that will be zoomed to /// </summary> [System.ComponentModel.Category("TaskResults"), System.ComponentModel.DefaultValue(10.0), System.ComponentModel.Description("Minimum width in map units that will be zoomed to"), System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute), System.ComponentModel.NotifyParentProperty(true)] public double MinWidthOfZoom { get { object o = StateManager.GetProperty("MinWidthOfZoom"); return (o == null) ? 10.0 : System.Convert.ToDouble(o); } set { if (value >= 0) { StateManager.SetProperty("MinWidthOfZoom", value); } else { throw new System.Exception(); } } } /// <summary> /// The percentage to expand the result set's extent by when zooming to results /// </summary> [System.ComponentModel.Category("TaskResults"), System.ComponentModel.DefaultValue(10.0), System.ComponentModel.Description("The percentage to expand the result set's extent by when zooming to results"), System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute), System.ComponentModel.NotifyParentProperty(true)] public double ZoomExtentExpansionPercent { get { object o = StateManager.GetProperty("ZoomExtentExpansionPercent"); return (o == null) ? 10.0 : System.Convert.ToDouble(o); } set { if (value >= 0) { StateManager.SetProperty("ZoomExtentExpansionPercent", value); } else { throw new System.Exception(); } } } /// <summary> /// Whether to show an activity indicator during task execution /// </summary> [System.ComponentModel.Category("TaskResults"), System.ComponentModel.DefaultValue(false), System.ComponentModel.Description("Whether to show an activity indicator during task execution"), System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute), System.ComponentModel.NotifyParentProperty(true)] public bool ShowTaskActivityIndicator { get { object o = StateManager.GetProperty("ShowTaskActivityIndicator"); return (o == null) ? false : System.Convert.ToBoolean(o); } set { StateManager.SetProperty("ShowTaskActivityIndicator", value); } } /// <summary> /// Whether to display task results in the control. Results can be zoomed to whether or not they are displayed. /// </summary> [System.ComponentModel.Category("TaskResults"), System.ComponentModel.DefaultValue(false), System.ComponentModel.Description( "Whether to display task results in the control. Results can be zoomed to whether or not they are displayed."), System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.Attribute), System.ComponentModel.NotifyParentProperty(true)] public bool DisplayTaskResults { get { object o = StateManager.GetProperty("DisplayTaskResults"); return (o == null) ? false : System.Convert.ToBoolean(o); } set { StateManager.SetProperty("DisplayTaskResults", value); } } #endregion #region ASP.NET WebControl Overrides - RenderContents // Overriden to add a design-time warning if the control has not been buddied // with a map protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) { if (this.DesignMode) this.RenderDesignTimeHtml(writer); else base.RenderContents(writer); } #endregion #region ITaskResultsContainer Members - StartTaskActivityIndicator, DisplayResults // Overriding the StartTaskActivityIndicator method in TaskResults is not possible since the method is // not marked virtual. Instead implement the method via ITaskResultsContainer and if necessary call the // method in the base class (TaskResults). void ESRI.ArcGIS.ADF.Web.UI.WebControls.ITaskResultsContainer.StartTaskActivityIndicator( ESRI.ArcGIS.ADF.Web.UI.WebControls.ITask task, string taskJobID) { if (ShowTaskActivityIndicator) { base.StartTaskActivityIndicator(task, taskJobID); } } // Overriding the DisplayResults method in TaskResults is not possible since the method is // not marked virtual. Instead implement the method via ITaskResultsContainer to zoom to // and/or select task results. Then call the base class's DisplayResults method if results // are to be displayed in this control. void ESRI.ArcGIS.ADF.Web.UI.WebControls.ITaskResultsContainer.DisplayResults( ESRI.ArcGIS.ADF.Web.UI.WebControls.ITask task, string taskJobID, object taskInputs, object taskResults) { // Throw an exception if the Map property is not set to a valid map. if (this.MapInstance == null) throw new System.Exception("You must set the Map property to a valid map"); // Return if the auto select and map zoom capabilities are disabled if (this.MaxResultsForMapZoom == 0 && this.MaxResultsForAutoSelect == 0) return; // Create the envlope to hold the extent that the map will be set to ESRI.ArcGIS.ADF.Web.Geometry.Envelope adfEnvelope = null; // Declare / initialize variables to use below int intTotalResultsCount = 0; bool doZoom = false; bool doSelect = false; System.Data.DataSet resultsDataSet = null; // Test to see if the results are contained in a TreeViePlusNode ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode treeViewPlusNode = taskResults as ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode; if (treeViewPlusNode != null) { // If it is a task results node count all the data // table rows in each GraphicsLayerNode intTotalResultsCount = this.CountAllResultsInNode(treeViewPlusNode); // Set flags indicating whether we should zoom or select based // on the size of the result set doZoom = (intTotalResultsCount <= this.MaxResultsForMapZoom); doSelect = (intTotalResultsCount <= this.MaxResultsForAutoSelect); // If we are going to zoom or select then process the results if (doZoom || doSelect) this.ProcessNode(treeViewPlusNode, doSelect, doZoom, ref adfEnvelope); } else { // Cast the results set to a DataSet resultsDataSet = taskResults as System.Data.DataSet; // Make sure we have a valid dataset with tables before iterating if (resultsDataSet != null && resultsDataSet.Tables.Count > 0) { // Count the number of results foreach (System.Data.DataTable dataTable in resultsDataSet.Tables) intTotalResultsCount += dataTable.Rows.Count; if (intTotalResultsCount > 0) { // Set flags indicating whether we should zoom or select based // on the size of the result set doZoom = (intTotalResultsCount <= this.MaxResultsForMapZoom); doSelect = (intTotalResultsCount <= this.MaxResultsForAutoSelect); // If we are going to zoom or select then process the results if (doZoom || doSelect) { foreach (System.Data.DataTable dataTable in resultsDataSet.Tables) { // Create a GraphicsLayer to pass to ProcessResultsLayer. This will be used to // get the extent of its features. Note that we create the layer from a copy // of the current data table because ToGraphicsLayer alters the passed-in table. ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer = ESRI.ArcGIS.ADF.Web.Converter.ToGraphicsLayer(dataTable.Copy()); if (graphicsLayer != null) this.ProcessResultsLayer(doSelect, doZoom, graphicsLayer, ref adfEnvelope); } } } } } // Zoom the map if the results set is equal to or less then the MaxResultsForZoom if (doZoom && adfEnvelope != null) { // Resize the envelope if it is smaller than the minimum extent size if (adfEnvelope.Width < this.MinWidthOfZoom && adfEnvelope.Height < this.MinWidthOfZoom) { double dblExpandX = (this.MinWidthOfZoom - adfEnvelope.Width) / 2; double dblExpandY = (this.MinWidthOfZoom - adfEnvelope.Height) / 2; adfEnvelope = new ESRI.ArcGIS.ADF.Web.Geometry.Envelope(adfEnvelope.XMin - dblExpandX, adfEnvelope.YMin - dblExpandY, adfEnvelope.XMax + dblExpandX, adfEnvelope.YMax + dblExpandY); } else if (adfEnvelope.Width < this.MinWidthOfZoom) { double dblExpandX = (this.MinWidthOfZoom - adfEnvelope.Width) / 2; adfEnvelope = new ESRI.ArcGIS.ADF.Web.Geometry.Envelope(adfEnvelope.XMin - dblExpandX, adfEnvelope.YMin, adfEnvelope.XMax + dblExpandX, adfEnvelope.YMax); } else if (adfEnvelope.Height < this.MinWidthOfZoom) { double dblExpandY = (this.MinWidthOfZoom - adfEnvelope.Height) / 2; adfEnvelope = new ESRI.ArcGIS.ADF.Web.Geometry.Envelope(adfEnvelope.XMin, adfEnvelope.YMin - dblExpandY, adfEnvelope.XMax, adfEnvelope.YMax + dblExpandY); } // Zoom out a little when you set the extent so you can see some area around the features if (this.ZoomExtentExpansionPercent > 0) adfEnvelope = adfEnvelope.Expand(ZoomExtentExpansionPercent); // Hold onto the old extent to test with later ESRI.ArcGIS.ADF.Web.Geometry.Envelope adfOriginalEnvelope = MapInstance.Extent.Clone() as ESRI.ArcGIS.ADF.Web.Geometry.Envelope; // Find the scale that the map will display at if the new envelope is applied double dblEnvelopeScale = 0; if (MapInstance.Extent.Width / adfEnvelope.Width < MapInstance.Extent.Height / adfEnvelope.Height) dblEnvelopeScale = MapInstance.Scale * (adfEnvelope.Width / MapInstance.Extent.Width); else dblEnvelopeScale = MapInstance.Scale * (adfEnvelope.Height / MapInstance.Extent.Height); // Remove callbacks from the map to ensure the extent change is processed this.MapInstance.CallbackResults.Clear(); //RemoveAllMapCallbacks(MapInstance); // If the data source is cached, zoom to the lowest cache level (largest map scale) that // encompasses the target extent ESRI.ArcGIS.ADF.Web.DataSources.TileCacheInfo tileCacheInfo = this.MapInstance.PrimaryMapResourceInstance.MapInformation.TileCacheInfo; if (tileCacheInfo != null) { // Iterate through the levels of the cache, starting with the lowest (largest // map scale) ESRI.ArcGIS.ADF.Web.DataSources.LodInfo[] lodInfo = tileCacheInfo.LodInfos; for (int i = tileCacheInfo.LodInfos.GetUpperBound(0); i > -1; i--) { // Check whether the current level displays at a scale greater than or equal // to that required for the target extent if (lodInfo[i].Scale >= dblEnvelopeScale) { // Center the map at the center of the target envelope this.MapInstance.CenterAt(new ESRI.ArcGIS.ADF.Web.Geometry.Point( adfEnvelope.XMin + (adfEnvelope.Width / 2), adfEnvelope.YMin + (adfEnvelope.Height / 2))); // Set the map level to the current one if it isn't already if (this.MapInstance.Level != i) this.MapInstance.Level = i; break; } } } else // This data is not cached so we don't need to worry about tile level. { // Check whether the current map scale matches the target scale. If so, we just need to pan, // so we use the CenterAt method. If we set the extent when the scale / map dimensions are // the same we can get artifacts in the graphics layer in IE6 if (System.Math.Round(MapInstance.Scale, 0) == System.Math.Round(dblEnvelopeScale, 0)) this.MapInstance.CenterAt(new ESRI.ArcGIS.ADF.Web.Geometry.Point( adfEnvelope.XMin + (adfEnvelope.Width / 2), adfEnvelope.YMin + (adfEnvelope.Height / 2))); else this.MapInstance.Extent = adfEnvelope; } } // If results are to be displayed in this control, invoke the base class's DisplayResults method. // This will handle adding the results to the control and the map. if (this.DisplayTaskResults) base.DisplayResults(task, taskJobID, taskInputs, taskResults); } #endregion #region Private Instance Methods - CountAllResultsInNode, ProcessNode, ProcessResultsLayer, RenderDesignTimeHtml // Gets the number of descendant result features contained by the passed-in node private int CountAllResultsInNode(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node) { int resultsCount = 0; // If the passed-in node is a graphics layer node, add the number of rows in its graphics layer // to the number of results ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = node as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode; if (graphicsLayerNode != null) resultsCount += graphicsLayerNode.Layer.Rows.Count; // Get the number of results contained by child nodes foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in node.Nodes) resultsCount += this.CountAllResultsInNode(childNode); return resultsCount; } // Calculates the containing envelope for and selects any results contained by the passed-in node private void ProcessNode(ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode node, bool doSelect, bool doZoom, ref ESRI.ArcGIS.ADF.Web.Geometry.Envelope adfEnvelope) { // If the node is a graphics layer node, pass its graphics layer to ProcessDataTable ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode graphicsLayerNode = node as ESRI.ArcGIS.ADF.Web.UI.WebControls.GraphicsLayerNode; if (graphicsLayerNode != null && graphicsLayerNode.Value != "Input Features") this.ProcessResultsLayer(doSelect, doZoom, graphicsLayerNode.Layer, ref adfEnvelope); // Process any child nodes foreach (ESRI.ArcGIS.ADF.Web.UI.WebControls.TreeViewPlusNode childNode in node.Nodes) this.ProcessNode(childNode, doSelect, doZoom, ref adfEnvelope); } // Selects all of the passed-in layer's features and unions the layer's extent with the // passed-in envelope private void ProcessResultsLayer(bool doSelect, bool doZoom, ESRI.ArcGIS.ADF.Web.Display.Graphics.GraphicsLayer graphicsLayer, ref ESRI.ArcGIS.ADF.Web.Geometry.Envelope adfEnvelope) { if (graphicsLayer.Rows.Count > 0) { // Expand the new extent to include the extent of the graphics layer if (doZoom) { // Create a new envelope if the one passed-in isn't initialized if (adfEnvelope == null) adfEnvelope = new ESRI.ArcGIS.ADF.Web.Geometry.Envelope(); // Union the layer's extent with the envelope adfEnvelope.Union(graphicsLayer.FullExtent); } // Select the layer's features if (doSelect) { foreach (System.Data.DataRow dataRow in graphicsLayer.Rows) dataRow[graphicsLayer.IsSelectedColumn] = true; } } } // Renders the control at design-tiem. Displays a warning if the control has // not been buddied with a map private void RenderDesignTimeHtml(System.Web.UI.HtmlTextWriter writer) { // Create a table to format the design-time display System.Web.UI.WebControls.Table table = new System.Web.UI.WebControls.Table(); System.Web.UI.WebControls.TableRow row = new System.Web.UI.WebControls.TableRow(); table.CellPadding = 4; table.Rows.Add(row); // Add the control's ID System.Web.UI.WebControls.TableCell cell = new System.Web.UI.WebControls.TableCell(); row.Cells.Add(cell); cell.Style[System.Web.UI.HtmlTextWriterStyle.WhiteSpace] = "nowrap"; cell.Text = string.Format("{0}<br>", this.ClientID); cell.ColumnSpan = 2; row = new System.Web.UI.WebControls.TableRow(); table.Rows.Add(row); // Add the control type cell = new System.Web.UI.WebControls.TableCell(); row.Cells.Add(cell); cell.Text = "ZoomToResults WebControl"; cell.ColumnSpan = 2; // If necessary, add a warning that the control needs to be buddied to a map if (this.Map.Equals("(none)") || string.IsNullOrEmpty(this.Map)) { row = new System.Web.UI.WebControls.TableRow(); table.Rows.Add(row); cell = new System.Web.UI.WebControls.TableCell(); row.Cells.Add(cell); cell.ForeColor = System.Drawing.Color.Red; cell.Text = "Warning:"; cell = new System.Web.UI.WebControls.TableCell(); row.Cells.Add(cell); cell.Style[System.Web.UI.HtmlTextWriterStyle.WhiteSpace] = "nowrap"; cell.Text = "You must set the Map property."; } table.RenderControl(writer); } #endregion } }