frmClosestFacilitySolver.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. // //************************************************************************************* // ArcGIS Network Analyst extension - Closest Facility Demonstration // // This simple code shows how to : // 1) Open a workspace and open a Network DataSet // 2) Create a NAContext and its NASolver // 3) Load Incidents/Facilites from Feature Classes and create Network Locations // 4) Set the Solver parameters // 5) Solve a Closest Facility problem // 6) Read the CFRoutes output to display the total facilities // and the list of the routes found //************************************************************************************ using System; using System.Text; using System.Windows.Forms; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.NetworkAnalyst; namespace ClosestFacilitySolver { public partial class frmClosestFacilitySolver : Form { private INAContext m_NAContext; private readonly string OUTPUTCLASSNAME = "CFRoutes"; #region Main Form Constructor and Setup public frmClosestFacilitySolver() { InitializeComponent(); Initialize(); } /// <summary> /// Initialize the solver by calling the ArcGIS Network Analyst extension functions. /// </summary> private void Initialize() { IFeatureWorkspace featureWorkspace = null; INetworkDataset networkDataset = null; try { // Open Geodatabase and network dataset IWorkspace workspace = OpenWorkspace(Application.StartupPath + @"\..\..\..\..\..\Data\SanFrancisco\SanFrancisco.gdb"); networkDataset = OpenNetworkDataset(workspace, "Transportation", "Streets_ND"); featureWorkspace = workspace as IFeatureWorkspace; } catch (Exception ex) { System.Windows.Forms.MessageBox.Show("Unable to open dataset. Error Message: " + ex.Message); this.Close(); return; } // Create NAContext and NASolver CreateSolverContext(networkDataset); // Get available cost attributes from the network dataset INetworkAttribute networkAttribute; for (int i = 0; i < networkDataset.AttributeCount - 1; i++) { networkAttribute = networkDataset.get_Attribute(i); if (networkAttribute.UsageType == esriNetworkAttributeUsageType.esriNAUTCost) { cboCostAttribute.Items.Add(networkAttribute.Name); } } cboCostAttribute.SelectedIndex = 0; txtTargetFacility.Text = "1"; txtCutOff.Text = ""; // Load incidents from a feature class IFeatureClass inputFClass = featureWorkspace.OpenFeatureClass("Stores"); LoadNANetworkLocations("Incidents", inputFClass, 500); // Load facilities from a feature class inputFClass = featureWorkspace.OpenFeatureClass("FireStations"); LoadNANetworkLocations("Facilities", inputFClass, 500); //Create Layer for Network Dataset and add to ArcMap INetworkLayer networkLayer = new NetworkLayerClass(); networkLayer.NetworkDataset = networkDataset; var layer = networkLayer as ILayer; layer.Name = "Network Dataset"; axMapControl.AddLayer(layer, 0); //Create a Network Analysis Layer and add to ArcMap INALayer naLayer = m_NAContext.Solver.CreateLayer(m_NAContext); layer = naLayer as ILayer; layer.Name = m_NAContext.Solver.DisplayName; axMapControl.AddLayer(layer, 0); } #endregion #region Button Clicks /// <summary> /// Call the Closest Facility cost matrix solver and display the results /// </summary> /// <param name="sender">Sender of the event</param> /// <param name="e">Event</param> private void cmdSolve_Click(object sender, System.EventArgs e) { this.Cursor = Cursors.WaitCursor; lstOutput.Items.Clear(); IGPMessages gpMessages = new GPMessagesClass(); try { lstOutput.Items.Add("Solving..."); SetSolverSettings(); 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)); cmdSolve.Text = "Find Closest Facilities"; RefreshMapDisplay(); 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> /// <returns>NAContext</returns> public void CreateSolverContext(INetworkDataset networkDataset) { if (networkDataset == null) return; //Get the Data Element IDENetworkDataset deNDS = GetDENetworkDataset(networkDataset); INASolver naSolver = new NAClosestFacilitySolver(); m_NAContext = naSolver.CreateContext(deNDS, naSolver.Name); ((INAContextEdit)m_NAContext).Bind(networkDataset, new GPMessagesClass()); } /// <summary> /// Set solver settings /// </summary> /// <param name="strNAClassName">NAClass name</param> /// <param name="inputFC">Input feature class</param> /// <param name="maxSnapTolerance">Max snap tolerance</param> public void LoadNANetworkLocations(string strNAClassName, IFeatureClass inputFC, double maxSnapTolerance) { INamedSet classes = m_NAContext.NAClasses; INAClass naClass = classes.get_ItemByName(strNAClassName) as INAClass; // delete existing Locations except if that a barriers naClass.DeleteAllRows(); // Create a NAClassLoader and set the snap tolerance (meters unit) INAClassLoader classLoader = new NAClassLoader(); classLoader.Locator = m_NAContext.Locator; if (maxSnapTolerance > 0) ((INALocator3)classLoader.Locator).MaxSnapTolerance = maxSnapTolerance; classLoader.NAClass = naClass; //Create field map to automatically map fields from input class to NAClass INAClassFieldMap fieldMap = new NAClassFieldMapClass(); fieldMap.CreateMapping(naClass.ClassDefinition, inputFC.Fields); classLoader.FieldMap = fieldMap; // 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; IFeatureCursor featureCursor = inputFC.Search(null, true); classLoader.Load((ICursor)featureCursor, null, ref rowsIn, ref rowsLocated); //Message all of the network analysis agents that the analysis context has changed ((INAContextEdit)m_NAContext).ContextChanged(); } #endregion #region Post-Solve /// <summary> /// Display analysis results in the list box /// </summary> public void DisplayOutput() { ITable table = m_NAContext.NAClasses.get_ItemByName(OUTPUTCLASSNAME) as ITable; if (table == null) { lstOutput.Items.Add("Impossible to get the " + OUTPUTCLASSNAME + " table"); } lstOutput.Items.Add("Number facilities found " + table.RowCount(null).ToString()); lstOutput.Items.Add(""); if (table.RowCount(null) > 0) { lstOutput.Items.Add("IncidentID, FacilityID,FacilityRank,Total_" + cboCostAttribute.Text); double total_impedance; long incidentID; long facilityID; long facilityRank; ICursor cursor; IRow row; cursor = table.Search(null, false); row = cursor.NextRow(); while (row != null) { incidentID = long.Parse(row.get_Value(table.FindField("IncidentID")).ToString()); facilityID = long.Parse(row.get_Value(table.FindField("FacilityID")).ToString()); facilityRank = long.Parse(row.get_Value(table.FindField("FacilityRank")).ToString()); total_impedance = double.Parse(row.get_Value(table.FindField("Total_" + cboCostAttribute.Text)).ToString()); lstOutput.Items.Add(incidentID.ToString() + ",\t" + facilityID.ToString() + ",\t" + facilityRank.ToString() + ",\t" + total_impedance.ToString("F2")); row = cursor.NextRow(); } } lstOutput.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(); } /// <summary> /// Refresh the map display /// <summary> public void RefreshMapDisplay() { // Zoom to the extent of the service areas IGeoDataset geoDataset = m_NAContext.NAClasses.get_ItemByName(OUTPUTCLASSNAME) as IGeoDataset; IEnvelope envelope = geoDataset.Extent; if (!envelope.IsEmpty) { envelope.Expand(1.1, 1.1, true); axMapControl.Extent = envelope; m_NAContext.Solver.UpdateLayer(axMapControl.get_Layer(0) as INALayer); } axMapControl.Refresh(); } #endregion #region Solver Settings /// <summary> /// Set solver settings /// </summary> public void SetSolverSettings() { //Set Route specific Settings INASolver naSolver = m_NAContext.Solver; INAClosestFacilitySolver cfSolver = naSolver as INAClosestFacilitySolver; if (txtCutOff.Text.Length > 0 && IsNumeric(txtCutOff.Text.Trim())) cfSolver.DefaultCutoff = txtCutOff.Text; else cfSolver.DefaultCutoff = null; if (txtTargetFacility.Text.Length > 0 && IsNumeric(txtTargetFacility.Text)) cfSolver.DefaultTargetFacilityCount = int.Parse(txtTargetFacility.Text); else cfSolver.DefaultTargetFacilityCount = 1; cfSolver.OutputLines = esriNAOutputLineType.esriNAOutputLineTrueShapeWithMeasure; cfSolver.TravelDirection = esriNATravelDirection.esriNATravelDirectionToFacility; // Set generic solver settings // Set the impedance attribute INASolverSettings naSolverSettings; naSolverSettings = naSolver as INASolverSettings; naSolverSettings.ImpedanceAttributeName = cboCostAttribute.Text; // Set the OneWay Restriction if necessary IStringArray restrictions; restrictions = naSolverSettings.RestrictionAttributeNames; restrictions.RemoveAll(); if (chkUseRestriction.Checked) restrictions.Add("oneway"); naSolverSettings.RestrictionAttributeNames = restrictions; //Restrict UTurns naSolverSettings.RestrictUTurns = esriNetworkForwardStarBacktrack.esriNFSBNoBacktrack; naSolverSettings.IgnoreInvalidLocations = true; // Set the Hierarchy attribute naSolverSettings.UseHierarchy = chkUseHierarchy.Checked; if (naSolverSettings.UseHierarchy) naSolverSettings.HierarchyAttributeName = "HierarchyMultiNet"; // Do not forget to update the context after you set your impedance naSolver.UpdateContext(m_NAContext, GetDENetworkDataset(m_NAContext.NetworkDataset), new GPMessagesClass()); } /// <summary> /// Check whether a string represents a double value. /// </summary> /// <param name="str">String to test</param> /// <returns>bool</returns> private bool IsNumeric(string str) { try { double.Parse(str.Trim()); } catch (Exception) { return false; } return true; } #endregion } }