ArcObjects Library Reference  

frmVRPSolver

About the Vehicle routing problem solver Sample

[C#]

frmVRPSolver.cs

//*************************************************************************************
//       ArcGIS Network Analyst extension - 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;
using System.Text;


namespace VRP_CSharp
{
	public partial class frmVRPSolver : Form
	{
		private INAContext m_NAContext;
		private System.Collections.Hashtable m_unitTimeList;
		private System.Collections.Hashtable m_unitDistList;
        private readonly string OUTPUTCLASSNAME = "Routes";

		public frmVRPSolver()
		{
			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
			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)
		{
            this.Cursor = Cursors.WaitCursor;
            listOutput.Items.Clear();

            IGPMessages gpMessages = new GPMessagesClass();
            try
			{
                listOutput.Items.Add("Solving...");

				SetSolverSettings();

				// Solve
				m_NAContext.Solver.Solve(m_NAContext, gpMessages, null);

				// Get the VRP output
                DisplayOutput();
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}

            listOutput.Items.Add(GetGPMessagesAsString(gpMessages));
            cmdSolve.Text = "Find VRP Solution";

            RefreshMapDisplay();

            this.Cursor = Cursors.Default;
        }

        /// <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();
        }

		/// <summary>
		/// Get the VRP route output
		/// </summary>
        public void DisplayOutput()
		{
			// Display route information
            ITable naTable = m_NAContext.NAClasses.get_ItemByName(OUTPUTCLASSNAME) 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 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
            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">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

		#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 = "HierarchyMultiNet";

			// Do not forget to update the context after you set your impedance
			solver.UpdateContext(m_NAContext, GetDENetworkDataset(m_NAContext.NetworkDataset), new GPMessagesClass());
		}

		#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

'*************************************************************************************
'       ArcGIS Network Analyst extension - 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 ArcGIS Network Analyst extension 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

        Dim gpMessages As IGPMessages = New GPMessagesClass()
        Try
            listOutput.Items.Clear()
            cmdSolve.Text = "Solving..."

            SetSolverSettings()

            ' Solve
            m_NAContext.Solver.Solve(m_NAContext, gpMessages, Nothing)

            ' Get the VRP output
            DisplayOutput()

            ' 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

        listOutput.Items.Add(GetGPMessagesAsString(GPMessages))
    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

    ''' <summary>
    ''' Get the VRP route output
    ''' </summary>
    Public Sub DisplayOutput()
        ' 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 = "HierarchyMultiNet"
		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