frmClosestFacilitySolver.vb
' 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 an shapefile 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 '************************************************************************************ Imports ESRI.ArcGIS.Carto Imports ESRI.ArcGIS.esriSystem Imports ESRI.ArcGIS.Geodatabase Imports ESRI.ArcGIS.Geometry Imports ESRI.ArcGIS.NetworkAnalyst Public Class frmClosestFacilitySolver Private m_NAContext As INAContext Public Sub New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call Initialize() End Sub '********************************************************************************* ' Initialize the form, create a NA context, load some locations and draw the map '********************************************************************************* Private Sub Initialize() ' Open geodatabase and network dataset Dim featureWorkspace As IFeatureWorkspace = Nothing Dim networkDataset As INetworkDataset = Nothing Try Dim workspace As IWorkspace = OpenWorkspace(Application.StartupPath & "\..\..\..\..\..\Data\SanFrancisco\SanFrancisco.gdb") networkDataset = OpenNetworkDataset(workspace, "Transportation", "Streets_ND") featureWorkspace = TryCast(workspace, IFeatureWorkspace) Catch ex As Exception Windows.Forms.MessageBox.Show("Unable to open dataset. Error Message: " + ex.Message) Me.Close() Return End Try ' Create NAContext and NASolver m_NAContext = CreateSolverContext(networkDataset) ' Get Cost Attributes and populate the combo drop down box Dim networkAttribute As INetworkAttribute For i As Integer = 0 To networkDataset.AttributeCount - 2 networkAttribute = networkDataset.Attribute(i) If networkAttribute.UsageType = esriNetworkAttributeUsageType.esriNAUTCost Then cboCostAttribute.Items.Add(networkAttribute.Name) cboCostAttribute.SelectedIndex = 0 End If Next txtTargetFacility.Text = "1" txtCutOff.Text = "<None>" ' Load incidents from FC Dim inputFClass As IFeatureClass = featureWorkspace.OpenFeatureClass("Stores") LoadNANetworkLocations("Incidents", inputFClass, 100) ' Load facilities from FC inputFClass = featureWorkspace.OpenFeatureClass("FireStations") LoadNANetworkLocations("Facilities", inputFClass, 100) ' Create Layer for Network Dataset and add to ArcMap Dim networkLayer As INetworkLayer = New NetworkLayerClass networkLayer.NetworkDataset = networkDataset Dim layer As ILayer = TryCast(networkLayer, ILayer) layer.Name = "Network Dataset" axMapControl.AddLayer(layer, 0) ' Create a Network Analysis Layer and add to ArcMap Dim naLayer As INALayer = m_NAContext.Solver.CreateLayer(m_NAContext) layer = naLayer layer.Name = m_NAContext.Solver.DisplayName axMapControl.AddLayer(layer, 0) End Sub '********************************************************************************* ' ArcGIS Network Analyst extension functions ' ******************************************************************************** '********************************************************************************* ' Create NASolver and NAContext '********************************************************************************* Public Function CreateSolverContext(ByVal networkDataset As INetworkDataset) As INAContext ' Get the Data Element Dim deNDS As IDENetworkDataset = GetDENetworkDataset(networkDataset) Dim naSolver As INASolver = New NAClosestFacilitySolver Dim contextEdit As INAContextEdit = naSolver.CreateContext(deNDS, naSolver.Name) contextEdit.Bind(networkDataset, New GPMessagesClass) Return TryCast(contextEdit, INAContext) End Function '********************************************************************************* ' Load network locations '********************************************************************************* Public Sub LoadNANetworkLocations(ByVal strNAClassName As String, ByVal inputFC As IFeatureClass, ByVal maxSnapTolerance As Double) Dim classes As INamedSet = m_NAContext.NAClasses Dim naClass As INAClass = TryCast(classes.ItemByName(strNAClassName), INAClass) ' delete existing Locations except if that a barriers naClass.DeleteAllRows() ' Avoid loading network locations onto non-traversable portions of elements Dim locator As INALocator3 = TryCast(m_NAContext.Locator, INALocator3) locator.ExcludeRestrictedElements = True locator.CacheRestrictedElements(m_NAContext) ' Create a NAClassLoader and set the maximum snap tolerance (meters unit) Dim classLoader As INAClassLoader = New NAClassLoader classLoader.Locator = m_NAContext.Locator If maxSnapTolerance > 0 Then locator.MaxSnapTolerance = maxSnapTolerance End If classLoader.NAClass = naClass ' Create field map to automatically map fields from input class to naclass Dim fieldMap As INAClassFieldMap = New NAClassFieldMap fieldMap.CreateMapping(naClass.ClassDefinition, inputFC.Fields) classLoader.FieldMap = fieldMap ' Load Network Locations Dim rowsIn As Integer = 0 Dim rowsLocated As Integer = 0 Dim featureCursor As IFeatureCursor = inputFC.Search(Nothing, True) classLoader.Load(featureCursor, Nothing, rowsIn, rowsLocated) ' Message all of the network analysis agents that the analysis context has changed CType(m_NAContext, INAContextEdit).ContextChanged() End Sub '********************************************************************************* ' Set Solver Settings '********************************************************************************* Public Sub SetSolverSettings() 'Set Route specific Settings Dim naSolver As INASolver = m_NAContext.Solver Dim cfSolver As INAClosestFacilitySolver = TryCast(naSolver, INAClosestFacilitySolver) ' Set number of facilities to find If txtTargetFacility.Text.Length > 0 And IsNumeric(txtTargetFacility.Text) Then cfSolver.DefaultTargetFacilityCount = Integer.Parse(txtTargetFacility.Text) Else cfSolver.DefaultTargetFacilityCount = 1 End If ' Set impedance cutoff If txtCutOff.Text.Length > 0 And IsNumeric(txtCutOff.Text.Trim()) Then cfSolver.DefaultCutoff = txtCutOff.Text Else cfSolver.DefaultCutoff = Nothing End If cfSolver.OutputLines = esriNAOutputLineType.esriNAOutputLineTrueShapeWithMeasure cfSolver.TravelDirection = esriNATravelDirection.esriNATravelDirectionToFacility 'Set generic Solver settings ' set the impedance attribute Dim naSolverSettings As INASolverSettings = naSolver naSolverSettings.ImpedanceAttributeName = cboCostAttribute.Text ' Set the OneWay Restriction if necessary Dim restrictions As IStringArray = naSolverSettings.RestrictionAttributeNames restrictions.RemoveAll() If chkUseRestriction.Checked Then restrictions.Add("oneway") End If naSolverSettings.RestrictionAttributeNames = restrictions 'Restrict UTurns naSolverSettings.RestrictUTurns = esriNetworkForwardStarBacktrack.esriNFSBNoBacktrack naSolverSettings.IgnoreInvalidLocations = True ' Set the Hierarchy attribute naSolverSettings.UseHierarchy = chkUseHierarchy.Checked If naSolverSettings.UseHierarchy Then naSolverSettings.HierarchyAttributeName = "HierarchyMultiNet" End If ' Do not forget to update the context after you set your impedance naSolver.UpdateContext(m_NAContext, GetDENetworkDataset(m_NAContext.NetworkDataset), New GPMessagesClass) End Sub '********************************************************************************* ' Geodatabase functions '********************************************************************************* Public Function OpenWorkspace(ByVal strGDBName As String) As IWorkspace ' As Workspace Factories are Singleton objects, they must be instantiated with the Activator Dim workspaceFactory As IWorkspaceFactory = TryCast(Activator.CreateInstance(Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory")), IWorkspaceFactory) Return workspaceFactory.OpenFromFile(strGDBName, 0) End Function '********************************************************************************* ' Open the network dataset '********************************************************************************* Public Function OpenNetworkDataset(ByVal workspace As IWorkspace, ByVal featureDatasetName As String, ByVal strNDSName As String) As INetworkDataset ' Obtain the dataset container from the workspace Dim featureWorkspace As IFeatureWorkspace = TryCast(workspace, IFeatureWorkspace) Dim featureDataset As IFeatureDataset = featureWorkspace.OpenFeatureDataset(featureDatasetName) Dim featureDatasetExtensionContainer As IFeatureDatasetExtensionContainer = TryCast(featureDataset, IFeatureDatasetExtensionContainer) Dim featureDatasetExtension As IFeatureDatasetExtension = featureDatasetExtensionContainer.FindExtension(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset) Dim datasetContainer3 As IDatasetContainer3 = TryCast(featureDatasetExtension, IDatasetContainer3) ' Use the container to open the network dataset Dim dataset As Object = datasetContainer3.DatasetByName(ESRI.ArcGIS.Geodatabase.esriDatasetType.esriDTNetworkDataset, strNDSName) Return TryCast(dataset, INetworkDataset) End Function Public Function GetDENetworkDataset(ByVal pNetDataset As INetworkDataset) As IDENetworkDataset 'Cast from the Network Dataset to the DatasetComponent Dim dsComponent As IDatasetComponent = pNetDataset 'Get the Data Element Return TryCast(dsComponent.DataElement, IDENetworkDataset) End Function Private Sub cmdSolve_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSolve.Click Dim gpMessages As IGPMessages = New GPMessagesClass Try lstOutput.Items.Clear() lstOutput.Items.Add("Solving...") SetSolverSettings() ' Solve If Not m_NAContext.Solver.Solve(m_NAContext, gpMessages, Nothing) Then lstOutput.Items.Add("Partial Result") End If DisplayOutput() Catch ee As Exception lstOutput.Items.Add("Failure: " + ee.Message) End Try lstOutput.Items.Add(GetGPMessagesAsString(gpMessages)) cmdSolve.Text = "Find Closest Facilities" End Sub Private Sub UpdateMapDisplayAfterSolve() ' Zoom to the extent of the service areas Dim geoDataset As IGeoDataset = TryCast(m_NAContext.NAClasses.ItemByName("CFRoutes"), IGeoDataset) Dim envelope As IEnvelope = geoDataset.Extent If (Not envelope.IsEmpty) Then 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(TryCast(axMapControl.get_Layer(0), INALayer)) End If axMapControl.Refresh() End Sub '********************************************************************************* ' Gather the error/warning/informative messages from GPMessages '********************************************************************************* Public Function GetGPMessagesAsString(ByVal gpMessages As IGPMessages) As String Dim messages As System.Text.StringBuilder = New System.Text.StringBuilder() If Not gpMessages Is Nothing Then Dim i As Integer For i = 0 To gpMessages.Count - 1 Dim gpMessage As IGPMessage = gpMessages.GetMessage(i) Dim message As String = gpMessage.Description Select Case gpMessage.Type Case esriGPMessageType.esriGPMessageTypeError messages.AppendLine("Error " + gpMessage.ErrorCode.ToString + ": " + message) Case esriGPMessageType.esriGPMessageTypeWarning messages.AppendLine("Warning: " + message) Case Else messages.AppendLine("Information: " + message) End Select Next End If Return messages.ToString() End Function ' Get the Impedance Cost form the CFRoute Class Output Public Sub DisplayOutput() Dim strNAClass As String = "CFRoutes" Dim table As ITable = m_NAContext.NAClasses.ItemByName(strNAClass) If table Is Nothing Then lstOutput.Items.Add("Impossible to get the " + strNAClass + " table") End If lstOutput.Items.Add("Number facilities found " + table.RowCount(Nothing).ToString()) lstOutput.Items.Add("") If table.RowCount(Nothing) > 0 Then lstOutput.Items.Add("IncidentID, FacilityID, FacilityRank, Total_" + cboCostAttribute.Text) Dim total_impedance As Double Dim incidentID As Long Dim facilityID As Long Dim facilityRank As Long Dim cursor As ICursor Dim row As IRow cursor = table.Search(Nothing, False) row = cursor.NextRow() While Not row Is Nothing incidentID = Long.Parse(row.Value(table.FindField("IncidentID")).ToString()) facilityID = Long.Parse(row.Value(table.FindField("FacilityID")).ToString()) facilityRank = Long.Parse(row.Value(table.FindField("FacilityRank")).ToString()) total_impedance = Double.Parse(row.Value(table.FindField("Total_" + cboCostAttribute.Text)).ToString()) lstOutput.Items.Add(incidentID.ToString() + "," + vbTab + facilityID.ToString() + "," + vbTab + facilityRank.ToString() + "," + vbTab + total_impedance.ToString("F2")) row = cursor.NextRow() End While End If lstOutput.Refresh() End Sub Private Function IsNumeric(ByVal str As String) As Boolean Try Double.Parse(str.Trim()) Catch Return False End Try Return True End Function End Class