frmServiceAreaSolver.cs
// Copyright 2012 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.Windows.Forms; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.NetworkAnalyst; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Geometry; using System.Text; namespace ServiceAreaSolver { public partial class frmServiceAreaSolver : Form { private INAContext m_NAContext; #region Main Form Constructor and Setup /// <summary> /// Initialize the solver by calling the ArcGIS Network Analyst extension functions. /// </summary> public frmServiceAreaSolver() { InitializeComponent(); Initialize(); } /// <summary> /// Set up the default values on the form /// </summary> private void Initialize() { txtCutOff.Text = "5"; lstOutput.Items.Clear(); cbCostAttribute.Items.Clear(); ckbUseRestriction.Checked = false; axMapControl.ClearLayers(); txtWorkspacePath.Text = Application.StartupPath + @"\..\..\..\..\..\Data\SanFrancisco\SanFrancisco.gdb"; txtNetworkDataset.Text = "Streets_ND"; txtFeatureDataset.Text = "Transportation"; txtInputFacilities.Text = "Hospitals"; gbServiceAreaSolver.Enabled = false; } #endregion #region Button Clicks /// <summary> /// Call the Service Area solver and display the results /// </summary> /// <param name="sender">Sender of the event</param> /// <param name="e">Event</param> private void btnSolve_Click(object sender, EventArgs e) { this.Cursor = Cursors.WaitCursor; lstOutput.Items.Clear(); IGPMessages gpMessages = new GPMessagesClass(); try { lstOutput.Items.Clear(); lstOutput.Items.Add("Solving..."); ConfigureSolverSettings(); if (m_NAContext.Solver.Solve(m_NAContext, gpMessages, null)) lstOutput.Items.Add("Partial Solve Generated."); DisplayOutput(); } catch (Exception ee) { lstOutput.Items.Add("Failure: " + ee.Message); } lstOutput.Items.Add(GetGPMessagesAsString(gpMessages)); RefreshMapDisplay(); this.Cursor = Cursors.Default; } /// <summary> /// Open the network dataset and set up the map /// </summary> /// <param name="sender">Sender of the event</param> /// <param name="e">Event</param> private void btnLoadMap_Click(object sender, EventArgs e) { this.Cursor = Cursors.WaitCursor; gbServiceAreaSolver.Enabled = false; lstOutput.Items.Clear(); // Verify that the workspace is valid IWorkspace workspace = OpenWorkspace(txtWorkspacePath.Text); if (workspace != null) { // Open the network dataset and generate a solver/context INetworkDataset networkDataset = OpenNetworkDataset(workspace, txtFeatureDataset.Text, txtNetworkDataset.Text); IFeatureWorkspace featureWorkspace = workspace as IFeatureWorkspace; CreateSolverContext(networkDataset); if (m_NAContext != null) { LoadCostAttributes(networkDataset); if (LoadLocations(featureWorkspace)) { AddNetworkDatasetLayerToMap(networkDataset); AddNetworkAnalysisLayerToMap(); // work around a transparency issue IGeoDataset geoDataset = networkDataset as IGeoDataset; axMapControl.Extent = axMapControl.FullExtent; axMapControl.Extent = geoDataset.Extent; if (m_NAContext != null) gbServiceAreaSolver.Enabled = true; } } } this.Cursor = Cursors.Default; } #endregion #region Set up Context and Solver /// <summary> /// Geodatabase function: open work space /// </summary> /// <param name="strGDBName">Input file name</param> /// <returns>Workspace</returns> public IWorkspace OpenWorkspace(string strGDBName) { // As Workspace Factories are Singleton objects, they must be instantiated with the Activator var workspaceFactory = System.Activator.CreateInstance(System.Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory")) as ESRI.ArcGIS.Geodatabase.IWorkspaceFactory; if (!System.IO.Directory.Exists(strGDBName)) { MessageBox.Show("The workspace: " + strGDBName + " does not exist", "Workspace Error"); return null; } IWorkspace workspace = null; try { workspace = workspaceFactory.OpenFromFile(strGDBName, 0); } catch (Exception ex) { MessageBox.Show("Opening workspace failed: " + ex.Message, "Workspace Error"); } return workspace; } /// <summary> /// Geodatabase function: open network dataset /// </summary> /// <param name="workspace">Input workspace</param> /// <param name="strNDSName">Input network dataset name</param> /// <returns>NetworkDataset</returns> public INetworkDataset OpenNetworkDataset(IWorkspace workspace, string featureDatasetName, string strNDSName) { // Obtain the dataset container from the workspace var featureWorkspace = workspace as IFeatureWorkspace; ESRI.ArcGIS.Geodatabase.IFeatureDataset featureDataset = featureWorkspace.OpenFeatureDataset(featureDatasetName); var featureDatasetExtensionContainer = featureDataset as ESRI.ArcGIS.Geodatabase.IFeatureDatasetExtensionContainer; ESRI.ArcGIS.Geodatabase.IFeatureDatasetExtension featureDatasetExtension = featureDatasetExtensionContainer.FindExtension(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset); var datasetContainer3 = featureDatasetExtension as ESRI.ArcGIS.Geodatabase.IDatasetContainer3; // Use the container to open the network dataset. ESRI.ArcGIS.Geodatabase.IDataset dataset = datasetContainer3.get_DatasetByName(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset, strNDSName); return dataset as ESRI.ArcGIS.Geodatabase.INetworkDataset; } /// <summary> /// Geodatabase function: get network dataset /// </summary> /// <param name="networkDataset">Input network dataset</param> /// <returns>DE network dataset</returns> public IDENetworkDataset GetDENetworkDataset(INetworkDataset networkDataset) { // Cast from the network dataset to the DatasetComponent IDatasetComponent dsComponent = networkDataset as IDatasetComponent; // Get the data element return dsComponent.DataElement as IDENetworkDataset; } /// <summary> /// Create NASolver and NAContext /// </summary> /// <param name="networkDataset">Input network dataset</param> private void CreateSolverContext(INetworkDataset networkDataset) { if (networkDataset == null) return; //Get the Data Element IDENetworkDataset deNDS = GetDENetworkDataset(networkDataset); INASolver naSolver = new NAServiceAreaSolverClass(); m_NAContext = naSolver.CreateContext(deNDS, naSolver.Name); ((INAContextEdit)m_NAContext).Bind(networkDataset, new GPMessagesClass()); } #endregion #region Load Form Controls /// <summary> /// Find and load the cost attributes into a combo box /// <summary> private void LoadCostAttributes(INetworkDataset networkDataset) { cbCostAttribute.Items.Clear(); int attrCount = networkDataset.AttributeCount; for (int attrIndex = 0; attrIndex < attrCount; attrIndex++) { INetworkAttribute networkAttribute = networkDataset.get_Attribute(attrIndex); if (networkAttribute.UsageType == esriNetworkAttributeUsageType.esriNAUTCost) cbCostAttribute.Items.Add(networkAttribute.Name); } if (cbCostAttribute.Items.Count > 0) cbCostAttribute.SelectedIndex = 0; } /// <summary> /// Find and load the cost attributes into a combo box /// <summary> /// <param name="featureWorkspace">The workspace that holds the input feature class</param> /// <returns>Success</returns> private bool LoadLocations(IFeatureWorkspace featureWorkspace) { IFeatureClass inputFeatureClass = null; try { inputFeatureClass = featureWorkspace.OpenFeatureClass(txtInputFacilities.Text); } catch (Exception) { MessageBox.Show("Specified input feature class does not exist"); return false; } INamedSet classes = m_NAContext.NAClasses; INAClass naClass = classes.get_ItemByName("Facilities") as INAClass; // delete existing locations, except barriers naClass.DeleteAllRows(); // Create a NAClassLoader and set the snap tolerance (meters unit) INAClassLoader naClassLoader = new NAClassLoaderClass(); naClassLoader.Locator = m_NAContext.Locator; ((INALocator3)naClassLoader.Locator).MaxSnapTolerance = 500; naClassLoader.NAClass = naClass; // Create field map to automatically map fields from input class to NAClass INAClassFieldMap naClassFieldMap = new NAClassFieldMapClass(); naClassFieldMap.CreateMapping(naClass.ClassDefinition, inputFeatureClass.Fields); naClassLoader.FieldMap = naClassFieldMap; // Avoid loading network locations onto non-traversable portions of elements INALocator3 locator = m_NAContext.Locator as INALocator3; locator.ExcludeRestrictedElements = true; locator.CacheRestrictedElements(m_NAContext); // load network locations int rowsIn = 0; int rowsLocated = 0; naClassLoader.Load(inputFeatureClass.Search(null, true) as ICursor, null, ref rowsIn, ref rowsLocated); if (rowsLocated <= 0) { MessageBox.Show("Facilities were not loaded from input feature class"); return false; } // Message all of the network analysis agents that the analysis context has changed INAContextEdit naContextEdit = m_NAContext as INAContextEdit; naContextEdit.ContextChanged(); return true; } /// <summary> /// Create a layer from the context and add it to the map /// </summary> private void AddNetworkAnalysisLayerToMap() { ILayer layer = m_NAContext.Solver.CreateLayer(m_NAContext) as ILayer; layer.Name = m_NAContext.Solver.DisplayName; axMapControl.AddLayer(layer); } /// <summary> /// Create a layer for the network dataset and add it to the map /// </summary> private void AddNetworkDatasetLayerToMap(INetworkDataset networkDataset) { INetworkLayer networkLayer = new NetworkLayerClass(); networkLayer.NetworkDataset = networkDataset; ILayer layer = networkLayer as ILayer; layer.Name = "Network Dataset"; axMapControl.AddLayer(layer); } #endregion #region Solver Settings /// <summary> /// Prepare the solver /// </summary> private void ConfigureSolverSettings() { ConfigureSettingsSpecificToServiceAreaSolver(); ConfigureGenericSolverSettings(); UpdateContextAfterChangingSettings(); } /// <summary> /// Update settings that only apply to the Service Area /// </summary> private void ConfigureSettingsSpecificToServiceAreaSolver() { INAServiceAreaSolver naSASolver = m_NAContext.Solver as INAServiceAreaSolver; naSASolver.DefaultBreaks = ParseBreaks(txtCutOff.Text); naSASolver.MergeSimilarPolygonRanges = false; naSASolver.OutputPolygons = esriNAOutputPolygonType.esriNAOutputPolygonSimplified; naSASolver.OverlapLines = true; naSASolver.SplitLinesAtBreaks = false; naSASolver.TravelDirection = esriNATravelDirection.esriNATravelDirectionFromFacility; naSASolver.OutputLines = esriNAOutputLineType.esriNAOutputLineNone; } /// <summary> /// Update settings that apply to all solvers /// </summary> private void ConfigureGenericSolverSettings() { INASolverSettings naSolverSettings = m_NAContext.Solver as INASolverSettings; naSolverSettings.ImpedanceAttributeName = cbCostAttribute.Text; // set the oneway restriction, if necessary IStringArray restrictions = naSolverSettings.RestrictionAttributeNames; restrictions.RemoveAll(); if (ckbUseRestriction.Checked) restrictions.Add("Oneway"); naSolverSettings.RestrictionAttributeNames = restrictions; //naSolverSettings.RestrictUTurns = esriNetworkForwardStarBacktrack.esriNFSBNoBacktrack; } /// <summary> /// When the solver has been update, the context must be updated as well /// </summary> private void UpdateContextAfterChangingSettings() { IDatasetComponent datasetComponent = m_NAContext.NetworkDataset as IDatasetComponent; IDENetworkDataset deNetworkDataset = datasetComponent.DataElement as IDENetworkDataset; m_NAContext.Solver.UpdateContext(m_NAContext, deNetworkDataset, new GPMessagesClass()); } /// <summary> /// Prepare the text string for breaks /// </summary> private IDoubleArray ParseBreaks(string p) { String[] breaks = p.Split(' '); IDoubleArray pBrks = new DoubleArrayClass(); int firstIndex = breaks.GetLowerBound(0); int lastIndex = breaks.GetUpperBound(0); for (int splitIndex = firstIndex; splitIndex <= lastIndex; splitIndex++) { try { pBrks.Add(Convert.ToDouble(breaks[splitIndex])); } catch (FormatException) { MessageBox.Show("Breaks are not properly formatted. Use only digits separated by spaces"); pBrks.RemoveAll(); return pBrks; } } return pBrks; } #endregion #region Post-Solve /// <summary> /// Display analysis results in the list box /// </summary> private void DisplayOutput() { ITable table = m_NAContext.NAClasses.get_ItemByName("SAPolygons") as ITable; if (table.RowCount(null) > 0) { IGPMessage gpMessage = new GPMessageClass(); lstOutput.Items.Add("FacilityID, FromBreak, ToBreak"); ICursor cursor = table.Search(null, true); IRow row = cursor.NextRow(); while (row != null) { int facilityID = (int)row.get_Value(table.FindField("FacilityID")); double fromBreak = (double)row.get_Value(table.FindField("FromBreak")); double toBreak = (double)row.get_Value(table.FindField("ToBreak")); lstOutput.Items.Add(facilityID.ToString() + ", " + fromBreak.ToString("#####0.00") + ", " + toBreak.ToString("#####0.00")); row = cursor.NextRow(); } } } /// <summary> /// Refresh the map display /// <summary> public void RefreshMapDisplay() { IGeoDataset geoDataset = m_NAContext.NAClasses.get_ItemByName("SAPolygons") as IGeoDataset; IEnvelope envelope = geoDataset.Extent; if (!envelope.IsEmpty) { envelope.Expand(1.1, 1.1, true); axMapControl.Extent = envelope; // Call this to update the renderer for the service area polygons // based on the new breaks. m_NAContext.Solver.UpdateLayer(axMapControl.get_Layer(0) as INALayer); } axMapControl.Refresh(); } /// <summary> /// Gather the error/warning/informative messages from GPMessages /// <summary> /// <param name="gpMessages">GPMessages container</param> /// <returns>string of all GPMessages</returns> public string GetGPMessagesAsString(IGPMessages gpMessages) { // Gather Error/Warning/Informative Messages var messages = new StringBuilder(); if (gpMessages != null) { for (int i = 0; i < gpMessages.Count; i++) { IGPMessage gpMessage = gpMessages.GetMessage(i); string message = gpMessage.Description; switch (gpMessages.GetMessage(i).Type) { case esriGPMessageType.esriGPMessageTypeError: messages.AppendLine("Error " + gpMessage.ErrorCode + ": " + message); break; case esriGPMessageType.esriGPMessageTypeWarning: messages.AppendLine("Warning: " + message); break; default: messages.AppendLine("Information: " + message); break; } } } return messages.ToString(); } #endregion } }