ArcObjects Library Reference  

SelectFeaturesTool

About the Using the traversal result to select source features Sample

[C#]

SelectFeaturesTool.cs

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 ESRI.ArcGIS.NetworkAnalystUI;

namespace NATraversalResult
{
	public class SelectFeaturesTool : ESRI.ArcGIS.Desktop.AddIns.Tool
	{
		INetworkAnalystExtension m_networkAnalystExtension;

		public SelectFeaturesTool()
		{
			m_networkAnalystExtension = ArcMap.Application.FindExtensionByName("Network Analyst") as INetworkAnalystExtension;
		}

		protected override void OnUpdate()
		{
			Enabled = ArcMap.Application != null;
		}

		private INALayer GetActiveAnalysisLayer()
		{
			if (m_networkAnalystExtension != null)
				return m_networkAnalystExtension.NAWindow.ActiveAnalysis;
			else
				return null;
		}

		protected override bool OnDeactivate()
		{
			return true;
		}

		/// <summary>
		/// Finds the closest feature to the point clicked that corresponds to a NATraversalResultElement in the active analysis layer's traversal result
		/// </summary>
		/// 
		protected override void OnMouseDown(MouseEventArgs e)
		{
			try
			{
				// Get the current network analysis traversal result
				INALayer naLayer = GetActiveAnalysisLayer();
				if (naLayer == null) throw new Exception("Null NALayer");

				INATraversalResult naTraversalResult = (INATraversalResult)naLayer.Context.Result;
				if (naTraversalResult == null || !((INAResult)naTraversalResult).HasValidResult)
					throw new Exception("Active analysis layer has an invalid result.");

				// Get the current map
				IMap map = ArcMap.Document.FocusMap;
				IActiveView activeView = ArcMap.Document.ActiveView;

				// Get the current point and buffer it into a polygon based on the search tolerance
				IPoint point = activeView.ScreenDisplay.DisplayTransformation.ToMapPoint(e.X, e.Y);

				IProximityOperator proximityOperator = point as IProximityOperator;
				ITopologicalOperator topologicalOperator = point as ITopologicalOperator;
				IPolygon polygon = (IPolygon)topologicalOperator.Buffer(activeView.Extent.Width / 20);
				double closestDistance = activeView.Extent.Width / 20;
				IFeature closestFeature = null;

				// Setup the spatial filter
				ISpatialFilter spatialFilter = new SpatialFilterClass();
				spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelEnvelopeIntersects;
				spatialFilter.Geometry = polygon.Envelope;

				//Loop through the layers in the map that participate in the network dataset
				//Find the closest feature
				IEnumLayer enumLayer = map.get_Layers(null, true);
				ILayer layer = enumLayer.Next();
				while (layer != null)
				{
					IFeatureLayer featureLayer = layer as IFeatureLayer;
					if (featureLayer != null)
					{
						if (featureLayer.Visible)
						{
							IFeatureClass featureClass = featureLayer.FeatureClass;

							// loop though all the sources in the network, trying to see if it corresponds to a source in the traversal result.
							for (int sourceIndex = 0; sourceIndex < naTraversalResult.SourceCount; sourceIndex++)
							{
								// Check to see if the feature class matches the traversal result source's feature class
								ITable currentTable = featureClass as ITable;
								ITable sourceTable = naTraversalResult.get_Source(sourceIndex).Table;
								if (currentTable == sourceTable)
								{
									// Since we correspond to a source in the traversal result, find the closest feature to the point clicked.
									spatialFilter.GeometryField = featureClass.ShapeFieldName;
									IFeatureCursor featureCursor = featureClass.Search(spatialFilter, false);
									IFeature feature = featureCursor.NextFeature();
									while (feature != null)
									{
										double distance = proximityOperator.ReturnDistance(feature.Shape);

										if (distance < closestDistance)
										{
											closestFeature = feature;
											closestDistance = distance;
										}

										feature = featureCursor.NextFeature();
									}
								}
							}
						}
					}

					layer = enumLayer.Next();
				}

				if (closestFeature == null)
					MessageBox.Show("Could not find a feature");
				else
					SelectTraversedFeatures(closestFeature);  // Since we found a feature, select the features traversed to get to it.
			}
			catch (Exception exception)
			{
				MessageBox.Show(exception.Message, "Error");
			}

		}


		/// <summary>
		/// Selects all the features in the visible feature layers that correspond to NATraversalResultElements
		/// that were traversed to get to the selected feature.  Do this by using INATraversalResultQuery to walk
		/// the traversal result.
		/// </summary>
		/// 
		private void SelectTraversedFeatures(IFeature selectedFeature)
		{
			// Get reference to the current map
			IMap map = ArcMap.Document.FocusMap;

			// Get the NATraversalResult
			INALayer naLayer = GetActiveAnalysisLayer();
			INAContext naContext = naLayer.Context;
			INATraversalResult naTraversalResult = (INATraversalResult)naContext.Result;
			INATraversalResultEdit naTraversalResultEdit = (INATraversalResultEdit)naTraversalResult;
			INATraversalResultQuery naTraversalResultQuery = (INATraversalResultQuery)naTraversalResult;

			// Cache the FeatureClass of each input source
			// Also create a LongArray to hold the OIDs of features traversed
			// This makes the assumption that the sources go from 1 to N
			INATraversalResultSource selectedSource = null;
			IFeatureClass[] sourceFeatureClasses = new IFeatureClass[naTraversalResult.SourceCount + 1]; // (will never use index 0)
			ILongArray[] sourceOIDs = new ILongArray[naTraversalResult.SourceCount + 1]; // (will never use index 0)

			for (int sourceIndex = sourceFeatureClasses.GetLowerBound(0); sourceIndex <= sourceFeatureClasses.GetUpperBound(0); sourceIndex++)
			{
				INATraversalResultSource naTraversalResultSource = naTraversalResult.get_SourceByID(sourceIndex);

				if (naTraversalResultSource != null)
				{
					IFeatureClass featureClass = naTraversalResultSource.Table as IFeatureClass;
					if (featureClass != null)
					{
						sourceFeatureClasses[sourceIndex] = featureClass;

						// If this source is the one of the selected feature,
						// remember it for later when we find it's traversal elements
						if (featureClass == selectedFeature.Class)
							selectedSource = naTraversalResultSource;

						sourceOIDs[sourceIndex] = new LongArrayClass();
					}
				}
			}

			// Verify the selected feature's class is actually in the network
			if (selectedSource == null)
				throw (new Exception("The selected feature is not part of the network."));

			// Get the traversal edges corresponding to the selected feature
			// There may be more than one returned if there are multiple facilities
			// or if the edge was traversed in both directions.
			IQueryFilter queryFilter = new QueryFilterClass();
			queryFilter.WhereClause = "SourceID = " + selectedSource.ID + " and SourceOID = " + selectedFeature.OID;

			// Figure out if the selected feature is a point or line
			// and get the correct traversal feature class
			IFeatureClass traversalFClass;
			if (selectedFeature.Shape.GeometryType == esriGeometryType.esriGeometryPoint)
				traversalFClass = naTraversalResultQuery.get_FeatureClass(esriNetworkElementType.esriNETJunction);
			else if (selectedFeature.Shape.GeometryType == esriGeometryType.esriGeometryPolyline)
				traversalFClass = naTraversalResultQuery.get_FeatureClass(esriNetworkElementType.esriNETEdge);
			else
				throw (new Exception("You must start from a point or polyline"));

			if (traversalFClass.FeatureCount(null) == 0)
				throw (new Exception("The traversal result is empty.  Please click solve."));

			// Search through the traversal class to find those corresponding to the selected feature
			INATraversalResultElement naTraversalResultElement;
			IFeatureCursor featureCursor = traversalFClass.Search(queryFilter, true);
			IFeature feature = featureCursor.NextFeature();
			if (feature == null)
				throw (new Exception("You must pick a feature that was traversed."));

			// Loop through all the traversal result elements for that feature
			while (feature != null)
			{
				naTraversalResultElement = (INATraversalResultElement)feature;
				sourceOIDs[naTraversalResultElement.SourceID].Add(naTraversalResultElement.SourceOID);

				//Get the connected traversal elements going backwards
				OutputConnected(naTraversalResultQuery, naTraversalResultElement, esriRelDirection.esriRelDirectionBackward, sourceOIDs);

				feature = featureCursor.NextFeature();
			}

			// Select the features in the Map
			SelectFeatures(map, sourceFeatureClasses, sourceOIDs);
		}

		/// <summary> 
		/// This function selects the features corresponding to the arrays of feature classes and OIDs
		/// </summary>
		private void SelectFeatures(IMap map, IFeatureClass[] sourceFeatureClasses, ILongArray[] sourceOIDs)
		{
			IActiveView activeView = (IActiveView)map;

			IEnumLayer enumLayer = map.get_Layers(null, true);
			ILayer layer = enumLayer.Next();
			while (layer != null)
			{
				IFeatureLayer featureLayer = layer as IFeatureLayer;
				IFeatureSelection featureSelection = layer as IFeatureSelection;

				if ((featureLayer != null) && (featureSelection != null) && featureLayer.Visible && featureLayer.Selectable)
				{
					for (int sourceIndex = sourceFeatureClasses.GetLowerBound(0); sourceIndex <= sourceFeatureClasses.GetUpperBound(0); sourceIndex++)
					{
						if (featureLayer.FeatureClass == sourceFeatureClasses[sourceIndex])
						{
							//Clear the current selection on this layer
							featureSelection.Clear();

							activeView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, map, null);

							// Get the OIDS we traversed that we want to select
							ILongArray longArray = sourceOIDs[sourceIndex];

							if (longArray.Count > 0)
							{
								// We have some OIDs, ad them to the selection set
								ISelectionSet selectionSet = featureSelection.SelectionSet;

								// Transfer from LongArray to an array of longs
								int numIDs = longArray.Count;
								int[] oids = new int[numIDs];
								for (int oidIndex = oids.GetLowerBound(0); oidIndex < numIDs; oidIndex++)
									selectionSet.Add(longArray.get_Element(oidIndex));
							}
						}
					}
				}
				layer = enumLayer.Next();
			}

			// Refresh the selection and Map
			ISelectionEvents selectionEvents = (ISelectionEvents)map;
			selectionEvents.SelectionChanged();

			activeView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, map, null);
		}


		/// <summary> 
		/// This is a recursive function that simply finds the connected traversal elements.
		/// It adds the OID of the feature to the appropriate array of ObjectIDs.
		/// </summary>
		private void OutputConnected(INATraversalResultQuery naTraversalResultQuery, INATraversalResultElement inputElement, esriRelDirection searchDirection, ILongArray[] sourceOIDs)
		{
			IFeatureCursor featureCursor;
			if (inputElement.ElementType == esriNetworkElementType.esriNETJunction)
				featureCursor = naTraversalResultQuery.SearchConnected(inputElement, esriNetworkElementType.esriNETEdge, searchDirection, false);
			else
				featureCursor = naTraversalResultQuery.SearchConnected(inputElement, esriNetworkElementType.esriNETJunction, searchDirection, false);

			// Iterate through the connected features.
			// For each feature process it and then recursively call this function again
			// to process the connected features.
			INATraversalResultElement connectedElement = (INATraversalResultElement)featureCursor.NextFeature();
			while (connectedElement != null)
			{
				if (sourceOIDs[connectedElement.SourceID] != null)
					sourceOIDs[connectedElement.SourceID].Add(connectedElement.SourceOID);

				// Recursion
				OutputConnected(naTraversalResultQuery, connectedElement, searchDirection, sourceOIDs);

				connectedElement = (INATraversalResultElement)featureCursor.NextFeature();
			}

		}
	}

}

[Visual Basic .NET]

SelectFeaturesTool.vb

Imports System
Imports System.Windows.Forms
Imports System.Runtime.InteropServices
Imports ESRI.ArcGIS.Framework
Imports ESRI.ArcGIS.NetworkAnalyst
Imports ESRI.ArcGIS.Carto
Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.NetworkAnalystUI

Public Class SelectFeaturesTool
	Inherits ESRI.ArcGIS.Desktop.AddIns.Tool

	Dim m_networkAnalystExtension As INetworkAnalystExtension
	Public Sub New()
		m_networkAnalystExtension = My.ArcMap.Application.FindExtensionByName("Network Analyst")
	End Sub

	Protected Overrides Sub OnUpdate()
		Enabled = My.ArcMap.Application IsNot Nothing
	End Sub

	Private Function GetActiveAnalysisLayer() As INALayer
		If Not (m_networkAnalystExtension Is Nothing) Then
			Return m_networkAnalystExtension.NAWindow.ActiveAnalysis
		Else
			Return Nothing
		End If
	End Function

	Protected Overloads Overrides Function OnDeactivate() As Boolean
		Return True
	End Function

	''' <summary>
	''' Finds the closest feature to the point clicked that corresponds to a NATraversalResultElement in the active analysis layer's traversal result
	''' </summary>
	Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
		Try
			' Get the current network analysis traversal result
			Dim naLayer As INALayer = GetActiveAnalysisLayer()
			If (naLayer Is Nothing) Then
				Throw New Exception("Null NALayer")
			End If

			Dim naTraversalResult As INATraversalResult = CType(naLayer.Context.Result, INATraversalResult)
			If (naTraversalResult Is Nothing) Then
				Throw New Exception("Active analysis layer does not have a result")
			End If
			Dim naResult = CType(naTraversalResult, INAResult)
			If (Not naResult.HasValidResult) Then
				Throw New Exception("Active analysis layer has an invalid result.")
			End If

			' Get the current map
			Dim map As IMap = My.ArcMap.Document.FocusMap
			Dim activeView As IActiveView = My.ArcMap.Document.ActiveView

			' Get the current point and buffer it into a polygon based on the search tolerance
			Dim point As IPoint = activeView.ScreenDisplay.DisplayTransformation.ToMapPoint(e.X, e.Y)

			Dim proximityOperator As IProximityOperator = point
			Dim topologicalOperator As ITopologicalOperator = point
			Dim polygon As IPolygon = topologicalOperator.Buffer(activeView.Extent.Width / 20)
			Dim closestDistance As Double = activeView.Extent.Width / 20
			Dim closestFeature As IFeature = Nothing

			' Setup the spatial filter
			Dim spatialFilter As ISpatialFilter = New SpatialFilterClass()
			spatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelEnvelopeIntersects
			spatialFilter.Geometry = polygon.Envelope

			'Loop through the layers in the map that participate in the network dataset
			'Find the closest feature
			Dim layers As IEnumLayer = map.Layers(Nothing, True)
			Dim layer As ILayer = layers.Next()

			While Not layer Is Nothing

				If TypeOf layer Is IFeatureLayer Then

					Dim featureLayer As IFeatureLayer = layer

					If featureLayer.Visible Then
						Dim featureClass As IFeatureClass = featureLayer.FeatureClass

						' loop though all the sources in the network, trying to see if it corresponds to a source in the traversal result.
						Dim sourceIndex As Integer
						For sourceIndex = 0 To naTraversalResult.SourceCount - 1
							' Check to see if the feature class matches the traversal result source's feature class
							Dim currentTable As ITable = featureClass
							Dim sourceTable As ITable = naTraversalResult.Source(sourceIndex).Table
							If currentTable Is sourceTable Then
								' Since we correspond to a source in the traversal result, find the closest feature to the point clicked.
								spatialFilter.GeometryField = featureClass.ShapeFieldName
								Dim featureCursor As IFeatureCursor = featureClass.Search(spatialFilter, False)
								Dim feature As IFeature = featureCursor.NextFeature()
								While Not feature Is Nothing
									Dim distance As Double = proximityOperator.ReturnDistance(feature.Shape)

									If distance < closestDistance Then
										closestFeature = feature
										closestDistance = distance
									End If

									feature = featureCursor.NextFeature()
								End While
							End If
						Next
					End If
				End If

				layer = layers.Next()
			End While

			If closestFeature Is Nothing Then
				MessageBox.Show("Could not find a feature")
			Else
				SelectTraversedFeatures(closestFeature)	 'Since we found a feature, select the features traversed to get to it.
			End If
		Catch exception As Exception
			MessageBox.Show(exception.Message, "Error")
		End Try

	End Sub

	''' <summary>
	''' Selects all the features in the visible feature layers that correspond to NATraversalResultElements
	''' that were traversed to get to the selected feature.  Do this by using INATraversalResultQuery to walk
	''' the traversal result.
	''' </summary> 
	Private Sub SelectTraversedFeatures(ByVal selectedFeature As IFeature)
		' Get reference to the current map
		Dim map As IMap = My.ArcMap.Document.FocusMap

		' Get the NATraversalResult
		Dim naLayer As INALayer = GetActiveAnalysisLayer()
		Dim naContext As INAContext = naLayer.Context
		Dim naTraversalResult As INATraversalResult = CType(naContext.Result, INATraversalResult)
		Dim naTraversalResultEdit As INATraversalResultEdit = CType(naTraversalResult, INATraversalResultEdit)
		Dim naTraversalResultQuery As INATraversalResultQuery = CType(naTraversalResult, INATraversalResultQuery)

		' Cache the FeatureClass of each input source
		' Also create a LongArray to hold the OIDs of features traversed
		' This makes the assumption that the sources go from 1 to N
		Dim selectedSource As INATraversalResultSource = Nothing
		Dim sourceFeatureClasses As IFeatureClass() = New IFeatureClass(naTraversalResult.SourceCount + 1) {}
		Dim sourceOIDs As ILongArray() = New ILongArray(naTraversalResult.SourceCount + 1) {}

		Dim sourceIndex As Integer
		For sourceIndex = sourceFeatureClasses.GetLowerBound(0) To sourceFeatureClasses.GetUpperBound(0)
			Dim naTraversalResultSource As INATraversalResultSource = naTraversalResult.SourceByID(sourceIndex)

			If Not naTraversalResultSource Is Nothing Then
				Dim featureClass As IFeatureClass = naTraversalResultSource.Table
				If Not featureClass Is Nothing Then
					sourceFeatureClasses(sourceIndex) = featureClass

					' If this source is the one of the selected feature,
					' remember it for later when we find it's traversal elements
					If featureClass Is selectedFeature.Class Then
						selectedSource = naTraversalResultSource
					End If

					sourceOIDs(sourceIndex) = New LongArrayClass()
				End If
			End If
		Next

		' Verify the selected feature's class is actually in the network
		If selectedSource Is Nothing Then
			Throw (New Exception("The selected feature is not part of the network."))
		End If

		' Get the traversal edges corresponding to the selected feature
		' There may be more than one returned if there are multiple facilities
		' or if the edge was traversed in both directions.
		Dim queryFilter As IQueryFilter = New QueryFilterClass()
		queryFilter.WhereClause = "SourceID = " & selectedSource.ID & " and SourceOID = " & selectedFeature.OID

		' Figure out if the selected feature is a point or line
		' and get the correct traversal feature class
		Dim traversalFClass As IFeatureClass
		If selectedFeature.Shape.GeometryType = ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPoint Then
			traversalFClass = naTraversalResultQuery.FeatureClass(esriNetworkElementType.esriNETJunction)
		ElseIf selectedFeature.Shape.GeometryType = ESRI.ArcGIS.Geometry.esriGeometryType.esriGeometryPolyline Then
			traversalFClass = naTraversalResultQuery.FeatureClass(esriNetworkElementType.esriNETEdge)
		Else
			Throw (New Exception("You must start from a point or polyline"))
		End If

		If traversalFClass.FeatureCount(Nothing) = 0 Then
			Throw (New Exception("The traversal result is empty.  Please click solve."))
		End If

		' Search through the traversal class to find those corresponding to the selected feature
		Dim naTraversalResultElement As INATraversalResultElement
		Dim featureCursor As IFeatureCursor = traversalFClass.Search(queryFilter, True)
		Dim feature As IFeature = featureCursor.NextFeature()
		If feature Is Nothing Then
			Throw (New Exception("You must pick a feature that was traversed."))
		End If

		' Loop through all the traversal result elements for that feature
		While Not feature Is Nothing
			naTraversalResultElement = CType(feature, INATraversalResultElement)
			sourceOIDs(naTraversalResultElement.SourceID).Add(naTraversalResultElement.SourceOID)

			'Get the connected traversal elements going backwards
			OutputConnected(naTraversalResultQuery, naTraversalResultElement, esriRelDirection.esriRelDirectionBackward, sourceOIDs)

			feature = featureCursor.NextFeature()
		End While

		' Select the features in the Map
		SelectFeatures(map, sourceFeatureClasses, sourceOIDs)
	End Sub

	''' <summary> 
	''' This function selects the features corresponding to the arrays of feature classes and OIDs
	''' </summary>
	Private Sub SelectFeatures(ByVal map As IMap, ByVal sourceFeatureClasses() As IFeatureClass, ByVal sourceOIDs() As ILongArray)
		Dim layers As IEnumLayer = map.Layers(Nothing, True)
		Dim layer As ILayer = layers.Next()
		Dim activeView As IActiveView = CType(map, IActiveView)

		While Not layer Is Nothing
			If (TypeOf layer Is IFeatureLayer) And (TypeOf layer Is IFeatureSelection) Then

				Dim featureLayer As IFeatureLayer = layer
				Dim featureSelection As IFeatureSelection = layer

				If featureLayer.Visible And featureLayer.Selectable Then
					Dim sourceIndex As Integer
					For sourceIndex = sourceFeatureClasses.GetLowerBound(0) To sourceFeatureClasses.GetUpperBound(0)
						If featureLayer.FeatureClass Is sourceFeatureClasses(sourceIndex) Then
							'Clear the current selection on this layer
							featureSelection.Clear()
							activeView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, map, Nothing)

							' Get the OIDS we traversed that we want to select
							Dim longArray As ILongArray = sourceOIDs(sourceIndex)

							If longArray.Count > 0 Then
								' We have some OIDs, ad them to the selection set
								Dim selectionSet As ISelectionSet = featureSelection.SelectionSet

								' Transfer from LongArray to an array of longs
								Dim numIDs As Integer = longArray.Count
								Dim oids() As Integer = New Integer(numIDs) {}
								Dim oidIndex As Integer
								For oidIndex = oids.GetLowerBound(0) To numIDs - 1
									selectionSet.Add(longArray.Element(oidIndex))
								Next

							End If
						End If
					Next
				End If
			End If
			layer = layers.Next()
		End While

		' Refresh the selection and Map
		Dim selectionEvents As ISelectionEvents = CType(map, ISelectionEvents)
		selectionEvents.SelectionChanged()

		activeView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, map, Nothing)
	End Sub

	''' <summary> 
	''' This is a recursive function that simply finds the connected traversal elements.
	''' It adds the OID of the feature to the appropriate array of ObjectIDs.
	''' </summary>
	Private Sub OutputConnected(ByVal naTraversalResultQuery As INATraversalResultQuery, ByVal inputElement As INATraversalResultElement, ByVal searchDirection As esriRelDirection, ByVal sourceOIDs As ILongArray())
		Dim featureCursor As IFeatureCursor
		If inputElement.ElementType = esriNetworkElementType.esriNETJunction Then
			featureCursor = naTraversalResultQuery.SearchConnected(inputElement, esriNetworkElementType.esriNETEdge, searchDirection, False)
		Else
			featureCursor = naTraversalResultQuery.SearchConnected(inputElement, esriNetworkElementType.esriNETJunction, searchDirection, False)
		End If
		Dim connectedElement As INATraversalResultElement = CType(featureCursor.NextFeature, INATraversalResultElement)
		While Not (connectedElement Is Nothing)
			If Not (sourceOIDs(connectedElement.SourceID) Is Nothing) Then
				sourceOIDs(connectedElement.SourceID).Add(connectedElement.SourceOID)
			End If
			OutputConnected(naTraversalResultQuery, connectedElement, searchDirection, sourceOIDs)
			connectedElement = CType(featureCursor.NextFeature, INATraversalResultElement)
		End While
	End Sub
End Class