About the Vehicle routing problem solver Sample
[C#]
frmVRPSolver.cs
//************************************************************************************* // Network Analyst - VRP Solver 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 Orders, Routes, Depots and Breaks from Feature Classes (or Table) and create Network Locations // 4) Set the Solver parameters // 5) Solve a VRP problem // 6) Read the VRP output to display the Route and Break output information //************************************************************************************ using System; 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 VRP_CSharp { public partial class frmVRPSolver : Form { private INAContext m_NAContext; private System.Collections.Hashtable m_unitTimeList; private System.Collections.Hashtable m_unitDistList; public frmVRPSolver() { InitializeComponent(); Initialize(); } /// <summary> /// Initialize the solver by calling the network analyst 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 m_NAContext = CreateSolverContext(networkDataset); // Get available cost attributes from the network dataset INetworkAttribute networkAttribute; for (int i = 0; i < networkDataset.AttributeCount; i++) { networkAttribute = networkDataset.get_Attribute(i); if (networkAttribute.UsageType == esriNetworkAttributeUsageType.esriNAUTCost) { string unitType = GetAttributeUnitType(networkAttribute.Units); if (unitType == "Time") { comboTimeAttribute.Items.Add(networkAttribute.Name); } else if (unitType == "Distance") { comboDistanceAttribute.Items.Add(networkAttribute.Name); } } } comboTimeAttribute.SelectedIndex = 0; comboDistanceAttribute.SelectedIndex = 0; // Populate time field unit in comboBox m_unitTimeList = new System.Collections.Hashtable(); m_unitTimeList.Add("Seconds", esriNetworkAttributeUnits.esriNAUSeconds); m_unitTimeList.Add("Minutes", esriNetworkAttributeUnits.esriNAUMinutes); foreach (System.Collections.DictionaryEntry timeUnit in m_unitTimeList) { comboTimeUnits.Items.Add(timeUnit.Key.ToString()); } comboTimeUnits.SelectedIndex = 1; // Populate distance field unit in comboBox m_unitDistList = new System.Collections.Hashtable(); m_unitDistList.Add("Miles", esriNetworkAttributeUnits.esriNAUMiles); m_unitDistList.Add("Meters", esriNetworkAttributeUnits.esriNAUMeters); foreach (System.Collections.DictionaryEntry distUnit in m_unitDistList) { comboDistUnits.Items.Add(distUnit.Key.ToString()); } comboDistUnits.SelectedIndex = 0; // Populate time window importance attribute in comboBox comboTWImportance.Items.Add("High"); comboTWImportance.Items.Add("Medium"); comboTWImportance.Items.Add("Low"); comboTWImportance.SelectedIndex = 0; // Load locations IFeatureClass inputFClass = featureWorkspace.OpenFeatureClass("Stores"); LoadNANetworkLocations("Orders", inputFClass as ITable); inputFClass = featureWorkspace.OpenFeatureClass("DistributionCenter"); LoadNANetworkLocations("Depots", inputFClass as ITable); inputFClass = featureWorkspace.OpenFeatureClass("Routes"); LoadNANetworkLocations("Routes", inputFClass as ITable); ITable inputTable = featureWorkspace.OpenTable("Breaks"); LoadNANetworkLocations("Breaks", inputTable); // Create layer for network dataset and add to ArcMap INetworkLayer networkLayer = new NetworkLayerClass(); networkLayer.NetworkDataset = networkDataset; ILayer 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); } /// <summary> /// Call VRP 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, EventArgs e) { try { listOutput.Items.Clear(); cmdSolve.Text = "Solving..."; SetSolverSettings(); // Solve IGPMessages gpMessages = new GPMessagesClass(); m_NAContext.Solver.Solve(m_NAContext, gpMessages, null); // Get the VRP output GetVRPOutput(); // Display Error/Warning/Informative Messages if (gpMessages != null) { for (int i = 0; i < gpMessages.Count; i++) { switch (gpMessages.GetMessage(i).Type) { case esriGPMessageType.esriGPMessageTypeError: listOutput.Items.Add("Error: " + gpMessages.GetMessage(i).ErrorCode.ToString() + " " + gpMessages.GetMessage(i).Description); break; case esriGPMessageType.esriGPMessageTypeWarning: listOutput.Items.Add("Warning: " + gpMessages.GetMessage(i).Description); break; default: listOutput.Items.Add("Information: " + gpMessages.GetMessage(i).Description); break; } } } // Zoom to the extent of the routes IGeoDataset gDataset = m_NAContext.NAClasses.get_ItemByName("Routes") as IGeoDataset; IEnvelope envelope = gDataset.Extent; if (!envelope.IsEmpty) { envelope.Expand(1.1, 1.1, true); axMapControl.Extent = envelope; } } catch (Exception ex) { MessageBox.Show(ex.Message); } finally { cmdSolve.Text = "Find VRP Solution"; } } /// <summary> /// Get the VRP route output /// </summary> public void GetVRPOutput() { // Display route information ITable naTable = m_NAContext.NAClasses.get_ItemByName("Routes") as ITable; if (naTable.RowCount(null) > 0) { listOutput.Items.Add("Route Name,\tOrder Count,\tTotal Cost,\tTotal Time,\tTotal Distance,\tStart Time,\tEnd Time:"); string routeName; long orderCount; double totalCost; double totalTime; double totalDistance; string routeStart; string routeEnd; ICursor naCursor = naTable.Search(null, false); IRow naRow = naCursor.NextRow(); // Display route details while (naRow != null) { routeName = naRow.get_Value(naTable.FindField("Name")).ToString(); orderCount = long.Parse(naRow.get_Value(naTable.FindField("OrderCount")).ToString()); totalCost = double.Parse(naRow.get_Value(naTable.FindField("TotalCost")).ToString()); totalTime = double.Parse(naRow.get_Value(naTable.FindField("TotalTime")).ToString()); totalDistance = double.Parse(naRow.get_Value(naTable.FindField("TotalDistance")).ToString()); routeStart = Convert.ToDateTime(naRow.get_Value(naTable.FindField("StartTime")).ToString()).ToString("T"); routeEnd = Convert.ToDateTime(naRow.get_Value(naTable.FindField("EndTime")).ToString()).ToString("T"); listOutput.Items.Add(routeName + ",\t\t" + orderCount.ToString() + ",\t\t" + totalCost.ToString("#0.00") + ",\t\t" + totalTime.ToString("#0.00") + ",\t\t" + totalDistance.ToString("#0.00") + ",\t\t" + routeStart + ",\t\t" + routeEnd); naRow = naCursor.NextRow(); } } listOutput.Items.Add(""); // Display lunch break information ITable naBreakTable = m_NAContext.NAClasses.get_ItemByName("Breaks") as ITable; if (naBreakTable.RowCount(null) > 0) { listOutput.Items.Add("Route Name,\tBreak Start Time,\tBreak End Time:"); ICursor naCursor = naBreakTable.Search(null, false); IRow naRow = naCursor.NextRow(); string routeName; string startTime; string endTime; // Display lunch details for each route while (naRow != null) { routeName = naRow.get_Value(naBreakTable.FindField("RouteName")).ToString(); startTime = Convert.ToDateTime(naRow.get_Value(naBreakTable.FindField("ArriveTime")).ToString()).ToString("T"); endTime = Convert.ToDateTime(naRow.get_Value(naBreakTable.FindField("DepartTime")).ToString()).ToString("T"); listOutput.Items.Add(routeName + ",\t\t" + startTime + ",\t\t" + endTime); naRow = naCursor.NextRow(); } } listOutput.Refresh(); } #region Network analyst functions /// <summary> /// Create NASolver and NAContext /// </summary> /// <param name="networkDataset">Input network dataset</param> /// <returns>NAContext</returns> public INAContext CreateSolverContext(INetworkDataset networkDataset) { // Get the data element IDENetworkDataset deNDS = GetDENetworkDataset(networkDataset); INASolver naSolver = new NAVRPSolver(); INAContextEdit contextEdit = naSolver.CreateContext(deNDS, naSolver.Name) as INAContextEdit; // Bind a context using the network dataset contextEdit.Bind(networkDataset, new GPMessagesClass()); return contextEdit as INAContext; } /// <summary> /// Load the input table and create field map to map fields from input table to NAClass /// </summary> /// <param name="strNAClassName">NAClass name</param> /// <param name="inputTable">Input table</param> public void LoadNANetworkLocations(string strNAClassName, ITable inputTable) { INamedSet classes = m_NAContext.NAClasses; INAClass naClass = classes.get_ItemByName(strNAClassName) as INAClass; // Delete existing rows from the specified NAClass naClass.DeleteAllRows(); // Create a NAClassLoader and set the snap tolerance (meters unit) INAClassLoader loader = new NAClassLoader(); loader.Locator = m_NAContext.Locator; loader.Locator.SnapTolerance = 100; loader.NAClass = naClass; // Create field map to automatically map fields from input table to NAclass INAClassFieldMap fieldMap = new NAClassFieldMapClass(); fieldMap.CreateMapping(naClass.ClassDefinition, inputTable.Fields); loader.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 input table int rowsIn = 0; int rowsLocated = 0; loader.Load(inputTable.Search(null, true), null, ref rowsIn, ref rowsLocated); // Message all of the network analysis agents that the analysis context has changed. INAContextEdit naContextEdit = m_NAContext as INAContextEdit; naContextEdit.ContextChanged(); } /// <summary> /// Set solver settings /// </summary> public void SetSolverSettings() { // Set VRP solver specific settings INASolver solver = m_NAContext.Solver; INAVRPSolver vrpSolver = solver as INAVRPSolver; // Both orders and routes have capacity count of 2 in the input shape files. User can modify the input data and update this value accordingly. vrpSolver.CapacityCount = 2; // Read the time and distance unit from comboBox vrpSolver.DistanceFieldUnits = (esriNetworkAttributeUnits)m_unitDistList[comboDistUnits.Items[comboDistUnits.SelectedIndex].ToString()]; vrpSolver.TimeFieldUnits = (esriNetworkAttributeUnits)m_unitTimeList[comboTimeUnits.Items[comboTimeUnits.SelectedIndex].ToString()]; // The value of time window violation penalty factor can be adjusted in terms of the user's preference. string importance = comboTWImportance.Items[comboTWImportance.SelectedIndex].ToString(); if (importance == "Low") vrpSolver.TimeWindowViolationPenaltyFactor = 0; else if (importance == "Medium") vrpSolver.TimeWindowViolationPenaltyFactor = 1; else if (importance == "High") vrpSolver.TimeWindowViolationPenaltyFactor = 10; // Set output line type vrpSolver.OutputLines = esriNAOutputLineType.esriNAOutputLineStraight; // Set generic solver settings // Set the impedance attribute INASolverSettings solverSettings = solver as INASolverSettings; solverSettings.ImpedanceAttributeName = comboTimeAttribute.Text; // Set the accumulated attribute IStringArray accumulatedAttributes = solverSettings.AccumulateAttributeNames; accumulatedAttributes.RemoveAll(); accumulatedAttributes.Insert(0, comboDistanceAttribute.Text); solverSettings.AccumulateAttributeNames = accumulatedAttributes; // Set the oneway restriction if necessary IStringArray restrictions = solverSettings.RestrictionAttributeNames; restrictions.RemoveAll(); if (checkUseRestriction.Checked) restrictions.Add("oneway"); solverSettings.RestrictionAttributeNames = restrictions; // Restrict UTurns solverSettings.RestrictUTurns = esriNetworkForwardStarBacktrack.esriNFSBNoBacktrack; // Set the hierarchy attribute solverSettings.UseHierarchy = checkUseHierarchy.Checked; if (solverSettings.UseHierarchy) solverSettings.HierarchyAttributeName = "hierarchy"; // Do not forget to update the context after you set your impedance solver.UpdateContext(m_NAContext, GetDENetworkDataset(m_NAContext.NetworkDataset), new GPMessagesClass()); } #endregion #region Geodatabase functions: open workspace and network dataset /// <summary> /// Geodatabase function: open workspace /// </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 IWorkspaceFactory workspaceFactory = Activator.CreateInstance(Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory")) as IWorkspaceFactory; return workspaceFactory.OpenFromFile(strGDBName, 0); } /// <summary> /// Geodatabase function: open network dataset /// </summary> /// <param name="workspace">Input workspace</param> /// <param name="strNDSName">Dataset name</param> /// <returns>Network dataset</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; } #endregion /// <summary> /// Check whether the attribute unit is time or distance unit. /// </summary> /// <param name="units">Input network attribute units</param> /// <returns>Unit type</returns> private string GetAttributeUnitType(esriNetworkAttributeUnits units) { string unitType = ""; switch (units) { case esriNetworkAttributeUnits.esriNAUDays: case esriNetworkAttributeUnits.esriNAUHours: case esriNetworkAttributeUnits.esriNAUMinutes: case esriNetworkAttributeUnits.esriNAUSeconds: unitType = "Time"; break; case esriNetworkAttributeUnits.esriNAUYards: case esriNetworkAttributeUnits.esriNAUMillimeters: case esriNetworkAttributeUnits.esriNAUMiles: case esriNetworkAttributeUnits.esriNAUMeters: case esriNetworkAttributeUnits.esriNAUKilometers: case esriNetworkAttributeUnits.esriNAUInches: case esriNetworkAttributeUnits.esriNAUFeet: case esriNetworkAttributeUnits.esriNAUDecimeters: case esriNetworkAttributeUnits.esriNAUNauticalMiles: case esriNetworkAttributeUnits.esriNAUCentimeters: unitType = "Distance"; break; default: listOutput.Items.Add("Failed to find Network Attribute Units."); break; } return unitType; } } }
[Visual Basic .NET]
frmVRPSolver.vb
'************************************************************************************* ' Network Analyst - VRP Solver 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 Orders, Routes, Depots and Breaks from Feature Classes (or Table) and create Network Locations ' 4) Set the Solver parameters ' 5) Solve a VRP problem ' 6) Read the VRP output to display the Route and Break output information '************************************************************************************ Imports Microsoft.VisualBasic Imports System Imports System.Windows.Forms Imports ESRI.ArcGIS.Geodatabase Imports ESRI.ArcGIS.NetworkAnalyst Imports ESRI.ArcGIS.esriSystem Imports ESRI.ArcGIS.Carto Imports ESRI.ArcGIS.Geometry Partial Public Class frmVRPSolver Inherits Form Private m_NAContext As INAContext Private m_unitTimeList As System.Collections.Hashtable Private m_unitDistList As System.Collections.Hashtable Public Sub New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call Initialize() End Sub ''' <summary> ''' Initialize the solver by calling the network analyst functions. ''' </summary> Private Sub Initialize() Dim featureWorkspace As IFeatureWorkspace = Nothing Dim networkDataset As INetworkDataset = Nothing ' Open network dataset 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 System.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 available cost attributes from the network dataset Dim networkAttribute As INetworkAttribute For i As Integer = 0 To networkDataset.AttributeCount - 1 networkAttribute = networkDataset.Attribute(i) If networkAttribute.UsageType = esriNetworkAttributeUsageType.esriNAUTCost Then Dim unitType As String = GetAttributeUnitType(networkAttribute.Units) If unitType = "Time" Then comboTimeAttribute.Items.Add(networkAttribute.Name) ElseIf unitType = "Distance" Then comboDistanceAttribute.Items.Add(networkAttribute.Name) End If End If Next i comboTimeAttribute.SelectedIndex = 0 comboDistanceAttribute.SelectedIndex = 0 ' Populate time field unit in comboBox m_unitTimeList = New System.Collections.Hashtable() m_unitTimeList.Add("Seconds", esriNetworkAttributeUnits.esriNAUSeconds) m_unitTimeList.Add("Minutes", esriNetworkAttributeUnits.esriNAUMinutes) For Each timeUnit As System.Collections.DictionaryEntry In m_unitTimeList comboTimeUnits.Items.Add(timeUnit.Key.ToString()) Next timeUnit comboTimeUnits.SelectedIndex = 1 ' Populate distance field unit in comboBox m_unitDistList = New System.Collections.Hashtable() m_unitDistList.Add("Miles", esriNetworkAttributeUnits.esriNAUMiles) m_unitDistList.Add("Meters", esriNetworkAttributeUnits.esriNAUMeters) For Each distUnit As System.Collections.DictionaryEntry In m_unitDistList comboDistUnits.Items.Add(distUnit.Key.ToString()) Next distUnit comboDistUnits.SelectedIndex = 0 ' Populate time window importance attribute in comboBox comboTWImportance.Items.Add("High") comboTWImportance.Items.Add("Medium") comboTWImportance.Items.Add("Low") comboTWImportance.SelectedIndex = 0 ' Load locations Dim inputFClass As IFeatureClass = featureWorkspace.OpenFeatureClass("Stores") LoadNANetworkLocations("Orders", TryCast(inputFClass, ITable)) inputFClass = featureWorkspace.OpenFeatureClass("DistributionCenter") LoadNANetworkLocations("Depots", TryCast(inputFClass, ITable)) inputFClass = featureWorkspace.OpenFeatureClass("Routes") LoadNANetworkLocations("Routes", TryCast(inputFClass, ITable)) Dim inputTable As ITable = featureWorkspace.OpenTable("Breaks") LoadNANetworkLocations("Breaks", inputTable) ' 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 = TryCast(naLayer, ILayer) layer.Name = m_NAContext.Solver.DisplayName AxMapControl.AddLayer(layer, 0) End Sub ''' <summary> ''' Call VRP solver and display the results ''' </summary> ''' <param name="sender">Sender of the event</param> ''' <param name="e">Event</param> Private Sub cmdSolve_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cmdSolve.Click Try listOutput.Items.Clear() cmdSolve.Text = "Solving..." SetSolverSettings() ' Solve Dim gpMessages As IGPMessages = New GPMessagesClass() m_NAContext.Solver.Solve(m_NAContext, gpMessages, Nothing) ' Get the VRP output GetVRPOutput() ' Display Error/Warning/Informative Messages If Not gpMessages Is Nothing Then For i As Integer = 0 To gpMessages.Count - 1 Select Case gpMessages.GetMessage(i).Type Case esriGPMessageType.esriGPMessageTypeError listOutput.Items.Add("Error: " & gpMessages.GetMessage(i).ErrorCode.ToString() & " " & gpMessages.GetMessage(i).Description) Case esriGPMessageType.esriGPMessageTypeWarning listOutput.Items.Add("Warning: " & gpMessages.GetMessage(i).Description) Case Else listOutput.Items.Add("Information: " & gpMessages.GetMessage(i).Description) End Select Next i End If ' Zoom to the extent of the routes Dim gDataset As IGeoDataset = TryCast(m_NAContext.NAClasses.ItemByName("Routes"), IGeoDataset) Dim envelope As IEnvelope = gDataset.Extent If (Not envelope.IsEmpty) Then envelope.Expand(1.1, 1.1, True) AxMapControl.Extent = envelope End If Catch ex As Exception MessageBox.Show(ex.Message) Finally cmdSolve.Text = "Find VRP Solution" End Try End Sub ''' <summary> ''' Get the VRP route output ''' </summary> Public Sub GetVRPOutput() ' Display route information Dim naTable As ITable = TryCast(m_NAContext.NAClasses.ItemByName("Routes"), ITable) If naTable.RowCount(Nothing) > 0 Then listOutput.Items.Add("Route Name," & Constants.vbTab & "Order Count," & Constants.vbTab & "Total Cost," & Constants.vbTab & "Total Time," & Constants.vbTab & "Total Distance," & Constants.vbTab & "Start Time," & Constants.vbTab & "End Time:") Dim routeName As String Dim orderCount As Long Dim totalCost As Double Dim totalTime As Double Dim totalDistance As Double Dim routeStart As String Dim routeEnd As String Dim naCursor As ICursor = naTable.Search(Nothing, False) Dim naRow As IRow = naCursor.NextRow() ' Display route details Do While Not naRow Is Nothing routeName = naRow.Value(naTable.FindField("Name")).ToString() orderCount = Long.Parse(naRow.Value(naTable.FindField("OrderCount")).ToString()) totalCost = Double.Parse(naRow.Value(naTable.FindField("TotalCost")).ToString()) totalTime = Double.Parse(naRow.Value(naTable.FindField("TotalTime")).ToString()) totalDistance = Double.Parse(naRow.Value(naTable.FindField("TotalDistance")).ToString()) routeStart = Convert.ToDateTime(naRow.Value(naTable.FindField("StartTime")).ToString()).ToString("T") routeEnd = Convert.ToDateTime(naRow.Value(naTable.FindField("EndTime")).ToString()).ToString("T") listOutput.Items.Add(routeName & "," & Constants.vbTab + Constants.vbTab + orderCount.ToString() & "," & Constants.vbTab + Constants.vbTab + totalCost.ToString("#0.00") & "," & Constants.vbTab + Constants.vbTab + totalTime.ToString("#0.00") & "," & Constants.vbTab + Constants.vbTab + totalDistance.ToString("#0.00") & "," & Constants.vbTab + Constants.vbTab & routeStart & "," & Constants.vbTab + Constants.vbTab & routeEnd) naRow = naCursor.NextRow() Loop End If listOutput.Items.Add("") ' Display lunch break information Dim naBreakTable As ITable = TryCast(m_NAContext.NAClasses.ItemByName("Breaks"), ITable) If naBreakTable.RowCount(Nothing) > 0 Then listOutput.Items.Add("Route Name," & Constants.vbTab & "Break Start Time," & Constants.vbTab & "Break End Time:") Dim naCursor As ICursor = naBreakTable.Search(Nothing, False) Dim naRow As IRow = naCursor.NextRow() Dim routeName As String Dim startTime As String Dim endTime As String ' Display lunch details for each route Do While Not naRow Is Nothing routeName = naRow.Value(naBreakTable.FindField("RouteName")).ToString() startTime = Convert.ToDateTime(naRow.Value(naBreakTable.FindField("ArriveTime")).ToString()).ToString("T") endTime = Convert.ToDateTime(naRow.Value(naBreakTable.FindField("DepartTime")).ToString()).ToString("T") listOutput.Items.Add(routeName & "," & Constants.vbTab + Constants.vbTab & startTime & "," & Constants.vbTab + Constants.vbTab & endTime) naRow = naCursor.NextRow() Loop End If listOutput.Refresh() End Sub #Region "Network analyst functions" ''' <summary> ''' Create NASolver and NAContext ''' </summary> ''' <param name="networkDataset">Input network dataset</param> ''' <returns>NAContext</returns> Public Function CreateSolverContext(ByVal networkDataset As INetworkDataset) As INAContext ' Get the data element Dim deNDS As IDENetworkDataset = GetDENetworkDataset(networkDataset) Dim naSolver As INASolver = New NAVRPSolver() Dim contextEdit As INAContextEdit = TryCast(naSolver.CreateContext(deNDS, naSolver.Name), INAContextEdit) ' Bind a context using the network dataset contextEdit.Bind(networkDataset, New GPMessagesClass()) Return TryCast(contextEdit, INAContext) End Function ''' <summary> ''' Load the input table and create field map to map fields from input table to NAClass ''' </summary> ''' <param name="strNAClassName">NAClass name</param> ''' <param name="inputTable">Input table</param> Public Sub LoadNANetworkLocations(ByVal strNAClassName As String, ByVal inputTable As ITable) Dim classes As INamedSet = m_NAContext.NAClasses Dim naClass As INAClass = TryCast(classes.ItemByName(strNAClassName), INAClass) ' Delete existing rows from the specified NAClass naClass.DeleteAllRows() ' Create a NAClassLoader and set the snap tolerance (meters unit) Dim loader As INAClassLoader = New NAClassLoader() loader.Locator = m_NAContext.Locator loader.Locator.SnapTolerance = 100 loader.NAClass = naClass ' Create field map to automatically map fields from input table to NAclass Dim fieldMap As INAClassFieldMap = New NAClassFieldMapClass() fieldMap.CreateMapping(naClass.ClassDefinition, inputTable.Fields) loader.FieldMap = fieldMap ' 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) ' Load input table Dim rowsIn As Integer = 0 Dim rowsLocated As Integer = 0 loader.Load(inputTable.Search(Nothing, True), Nothing, rowsIn, rowsLocated) ' Message all of the network analysis agents that the analysis context has changed. Dim naContextEdit As INAContextEdit = TryCast(m_NAContext, INAContextEdit) naContextEdit.ContextChanged() End Sub ''' <summary> ''' Set solver settings ''' </summary> Public Sub SetSolverSettings() ' Set VRP solver specific settings Dim solver As INASolver = m_NAContext.Solver Dim vrpSolver As INAVRPSolver = TryCast(solver, INAVRPSolver) ' Both orders and routes have capacity count of 2 in the input shape files. User can modify the input data and update this value accordingly. vrpSolver.CapacityCount = 2 ' Read the time and distance unit from comboBox vrpSolver.DistanceFieldUnits = CType(m_unitDistList(comboDistUnits.Items(comboDistUnits.SelectedIndex).ToString()), esriNetworkAttributeUnits) vrpSolver.TimeFieldUnits = CType(m_unitTimeList(comboTimeUnits.Items(comboTimeUnits.SelectedIndex).ToString()), esriNetworkAttributeUnits) ' The value of time window violation penalty factor can be adjusted in terms of the user's preference. Dim importance As String = comboTWImportance.Items(comboTWImportance.SelectedIndex).ToString() If importance = "Low" Then vrpSolver.TimeWindowViolationPenaltyFactor = 0 ElseIf importance = "Medium" Then vrpSolver.TimeWindowViolationPenaltyFactor = 1 ElseIf importance = "High" Then vrpSolver.TimeWindowViolationPenaltyFactor = 10 End If ' Set output line type vrpSolver.OutputLines = esriNAOutputLineType.esriNAOutputLineStraight ' Set generic solver settings ' Set the impedance attribute Dim solverSettings As INASolverSettings = TryCast(solver, INASolverSettings) solverSettings.ImpedanceAttributeName = comboTimeAttribute.Text ' Set the accumulated attribute Dim accumulatedAttributes As IStringArray = solverSettings.AccumulateAttributeNames accumulatedAttributes.RemoveAll() accumulatedAttributes.Insert(0, comboDistanceAttribute.Text) solverSettings.AccumulateAttributeNames = accumulatedAttributes ' Set the oneway restriction if necessary Dim restrictions As IStringArray = solverSettings.RestrictionAttributeNames restrictions.RemoveAll() If checkUseRestriction.Checked Then restrictions.Add("oneway") End If solverSettings.RestrictionAttributeNames = restrictions ' Restrict UTurns solverSettings.RestrictUTurns = esriNetworkForwardStarBacktrack.esriNFSBNoBacktrack ' Set the hierarchy attribute solverSettings.UseHierarchy = checkUseHierarchy.Checked If solverSettings.UseHierarchy Then solverSettings.HierarchyAttributeName = "hierarchy" End If ' Do not forget to update the context after you set your impedance solver.UpdateContext(m_NAContext, GetDENetworkDataset(m_NAContext.NetworkDataset), New GPMessagesClass()) End Sub #End Region #Region "Geodatabase functions: open workspace and network dataset" ''' <summary> ''' Geodatabase function: open workspace ''' </summary> ''' <param name="strGDBName">File name</param> ''' <returns>Workspace</returns> 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 ''' <summary> ''' Geodatabase function: open network dataset ''' </summary> ''' <param name="workspace">Work space</param> ''' <param name="strNDSName">Dataset name</param> ''' <returns>Network dataset</returns> 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 ''' <summary> ''' Geodatabase function: get network dataset ''' </summary> ''' <param name="networkDataset">Input network dataset</param> ''' <returns>DE network dataset</returns> Public Function GetDENetworkDataset(ByVal networkDataset As INetworkDataset) As IDENetworkDataset ' Cast from the network dataset to the DatasetComponent Dim dsComponent As IDatasetComponent = TryCast(networkDataset, IDatasetComponent) ' Get the data element Return TryCast(dsComponent.DataElement, IDENetworkDataset) End Function #End Region ''' <summary> ''' Check whether the attribute unit is time or distance unit. ''' </summary> ''' <param name="units">Input network attribute units</param> ''' <returns>Unit type</returns> Private Function GetAttributeUnitType(ByVal units As esriNetworkAttributeUnits) As String Dim unitType As String = "" Select Case units Case esriNetworkAttributeUnits.esriNAUDays, esriNetworkAttributeUnits.esriNAUHours, esriNetworkAttributeUnits.esriNAUMinutes, esriNetworkAttributeUnits.esriNAUSeconds unitType = "Time" Case esriNetworkAttributeUnits.esriNAUYards, esriNetworkAttributeUnits.esriNAUMillimeters, esriNetworkAttributeUnits.esriNAUMiles, esriNetworkAttributeUnits.esriNAUMeters, esriNetworkAttributeUnits.esriNAUKilometers, esriNetworkAttributeUnits.esriNAUInches, esriNetworkAttributeUnits.esriNAUFeet, esriNetworkAttributeUnits.esriNAUDecimeters, esriNetworkAttributeUnits.esriNAUNauticalMiles, esriNetworkAttributeUnits.esriNAUCentimeters unitType = "Distance" Case Else listOutput.Items.Add("Failed to find Network Attribute Units.") End Select Return unitType End Function End Class