ArcObjects Library Reference  

SignpostUtilities

About the Import signposts Sample

[C#]

SignpostUtilities.cs

using System;
using System.Runtime.InteropServices;
using System.Collections;

using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geoprocessing;
using ESRI.ArcGIS.Geometry;

namespace GPImportSignpostFunctions
{
	/// <summary>
	/// SignpostUtilities class has functions for creating and populating the signpost
	/// schema.
	/// </summary>
	public class SignpostUtilities
	{
		public static readonly int MaxBranchCount = 5;

		public struct FeatureData
		{
			public FeatureData(int ID, IGeometry geom)
			{
				OID = ID;
				feature = geom;
			}
			public int OID;
			public IGeometry feature;
		}

		public SignpostUtilities()
		{
		}

		public static IFeatureClass CreateSignsFeatureClass(IFeatureClass linesFeatureClass, string name)
		{
			// Locations are all relative to the location of the reference lines.
			// For geodatabase, signs feature class is at the same location and the streets table
			// is at the level of the containing feature dataset.
			// For shapefile, both are at the same location as the reference lines.

			// start with the initial set of required fields for a feature class

			IFeatureClassDescription fcDescription = new FeatureClassDescriptionClass();
			IObjectClassDescription ocDescription = fcDescription as IObjectClassDescription;
			IFieldsEdit outFields = ocDescription.RequiredFields as IFieldsEdit;

            // make the shape field to be of type polyline with the same spatial reference as the reference lines

            IField shapeField = outFields.get_Field(outFields.FindField(fcDescription.ShapeFieldName));
            IGeometryDefEdit geomDefEdit = shapeField.GeometryDef as IGeometryDefEdit;
            geomDefEdit.GeometryType_2 = esriGeometryType.esriGeometryPolyline;
            geomDefEdit.SpatialReference_2 = (linesFeatureClass as IGeoDataset).SpatialReference;

			// add the other fields to the feature class

			IFieldEdit field = new FieldClass();
			field.Name_2 = "ExitName";
			field.Type_2 = esriFieldType.esriFieldTypeString;
			field.Length_2 = 10;
			outFields.AddField(field);

			string currentNumber;

			for (int i = 0; i < MaxBranchCount; i++)
			{
				currentNumber = Convert.ToString(i);

				field = new FieldClass();
				field.Name_2 = "Branch" + currentNumber;
				field.Type_2 = esriFieldType.esriFieldTypeString;
				field.Length_2 = 75;
				outFields.AddField(field);

				field = new FieldClass();
				field.Name_2 = "Branch" + currentNumber + "Dir";
				field.Type_2 = esriFieldType.esriFieldTypeString;
				field.Length_2 = 5;
				outFields.AddField(field);

				field = new FieldClass();
				field.Name_2 = "Branch" + currentNumber + "Lng";
				field.Type_2 = esriFieldType.esriFieldTypeString;
				field.Length_2 = 2;
				outFields.AddField(field);

				field = new FieldClass();
				field.Name_2 = "Toward" + currentNumber;
				field.Type_2 = esriFieldType.esriFieldTypeString;
				field.Length_2 = 75;
				outFields.AddField(field);

				field = new FieldClass();
				field.Name_2 = "Toward" + currentNumber + "Lng";
				field.Type_2 = esriFieldType.esriFieldTypeString;
				field.Length_2 = 2;
				outFields.AddField(field);
			}

			// make the feature class

			IFeatureDataset pFeatureDataset = linesFeatureClass.FeatureDataset;
			IWorkspace pWorkspace = (linesFeatureClass as IDataset).Workspace;

			if (pFeatureDataset != null)
				return pFeatureDataset.CreateFeatureClass(name, outFields, ocDescription.InstanceCLSID, 
                    ocDescription.ClassExtensionCLSID, esriFeatureType.esriFTSimple, fcDescription.ShapeFieldName, "");
			else if (pWorkspace is IFeatureWorkspace)
				return (pWorkspace as IFeatureWorkspace).CreateFeatureClass(name, outFields, ocDescription.InstanceCLSID,
                    ocDescription.ClassExtensionCLSID, esriFeatureType.esriFTSimple, fcDescription.ShapeFieldName, "");
			else
				return null;   // not expected
		}

		public static ITable CreateSignsDetailTable(IFeatureClass linesFeatureClass, string name)
		{
			// Locations are all relative to the location of the reference lines.
			// For Geodatabase, signs feature class is at the same location and the streets table
			// is at the level of the containing feature dataset.
			// For shapefile, both are at the same location as the reference lines.

			// start with the initial set of required fields for a table

			IObjectClassDescription ocDescription = new ObjectClassDescriptionClass();
			IFieldsEdit outFields = ocDescription.RequiredFields as IFieldsEdit;

			// add the SignpostID field to the table

			IFieldEdit field = new FieldClass();
			field.Name_2 = "SignpostID";
			field.Type_2 = esriFieldType.esriFieldTypeInteger;
			outFields.AddField(field);

			// add the other fields to the table

			field = new FieldClass();
			field.Name_2 = "Sequence";
			field.Type_2 = esriFieldType.esriFieldTypeInteger;
			outFields.AddField(field);

			field = new FieldClass();
			field.Name_2 = "EdgeFCID";
			field.Type_2 = esriFieldType.esriFieldTypeInteger;
			outFields.AddField(field);

			field = new FieldClass();
			field.Name_2 = "EdgeFID";
			field.Type_2 = esriFieldType.esriFieldTypeInteger;
			outFields.AddField(field);

			field = new FieldClass();
			field.Name_2 = "EdgeFrmPos";
			field.Type_2 = esriFieldType.esriFieldTypeDouble;
			outFields.AddField(field);

			field = new FieldClass();
			field.Name_2 = "EdgeToPos";
			field.Type_2 = esriFieldType.esriFieldTypeDouble;
			outFields.AddField(field);

			// make the table

			IFeatureDataset featureDataset = linesFeatureClass.FeatureDataset;
			IWorkspace workspace = (linesFeatureClass as IDataset).Workspace;

			if (featureDataset != null)
			{
				// up a level
				IFeatureWorkspace createWS = featureDataset.Workspace as IFeatureWorkspace;
				return createWS.CreateTable(name, outFields, ocDescription.InstanceCLSID, null, "");
			}
			else if (workspace is IFeatureWorkspace)
				return (workspace as IFeatureWorkspace).CreateTable(name, outFields, ocDescription.InstanceCLSID, null, "");
			else
				return null;   // not expected        
		}

		public static Hashtable FillFeatureCache(ITable inputSignsTable, int inFromIDFI, int inToIDFI,
												 IFeatureClass inputLineFeatures, string linesIDFieldName,
												 ITrackCancel trackcancel)
		{
			// make and fill a SortedList from the IDs referenced in the table

			// for MultiNet data, there is only one ID field, so its index will be 
			// passed in as inFromIDFI, while -1 will be passed in to inToIDFI.

			SortedList IDs = new System.Collections.SortedList();

			ICursor inCursor = inputSignsTable.Search(null, true);
			IRow row;

			long fromID, toID;
			bool exists;
			int cancelCheckInterval = 100;

			if (inToIDFI == -1)
			{
				while ((row = inCursor.NextRow()) != null)
				{
					fromID = Convert.ToInt64(row.get_Value(inFromIDFI));

					exists = IDs.Contains(fromID);
					if (!exists)
						IDs.Add(fromID, fromID);
				}
			}
			else
			{
				while ((row = inCursor.NextRow()) != null)
				{
					fromID = Convert.ToInt64(row.get_Value(inFromIDFI));
					toID = Convert.ToInt64(row.get_Value(inToIDFI));

					exists = IDs.Contains(fromID);
					if (!exists)
						IDs.Add(fromID, fromID);

					exists = IDs.Contains(toID);
					if (!exists)
						IDs.Add(toID, toID);
				}
			}

			// make the query filter for fetching features

			IQueryFilter queryFilter = new QueryFilterClass();
			queryFilter.SubFields = "*";

			// Now fetch batches of features

			long currID;
			int numFeaturesPerQuery = 200;
			int numToFetch = IDs.Count;
			int totalRemaining, totalDone = 0;

			int linesIDFieldIndex = inputLineFeatures.FindField(linesIDFieldName);

			Hashtable outputFeatures = new System.Collections.Hashtable((int)numToFetch);

			if (numFeaturesPerQuery > numToFetch)
				numFeaturesPerQuery = numToFetch;

			while (totalDone < numToFetch)
			{
				// Populate the QueryDef Where clause IN() statement for the current batch of features.
				// This is going to be very slow unless linesIDFieldName is indexed and this is why
				// we added a warning to the GP message window if this is the case.  If you cannot
				// index linesIDFieldName, then this code would run faster scanning the whole feature
				// class looking for the records we need (no Where clause).

				string whereClause = linesIDFieldName + " IN(";

				for (int i = 0; i < numFeaturesPerQuery; i++)
				{
					currID = Convert.ToInt64(IDs.GetByIndex(totalDone + i));
					whereClause += Convert.ToString(currID);
					if (i != (numFeaturesPerQuery - 1))
						whereClause += ",";
					else
						whereClause += ")";
				}

				queryFilter.WhereClause = whereClause;

				// select the features

				IFeatureCursor inputFeatureCursor = inputLineFeatures.Search(queryFilter, false);

				// get the features

				IFeature feature;

				while ((feature = inputFeatureCursor.NextFeature()) != null)
				{
					// keep a copy of the OID and shape of feature - skip records that cause errors
					// (perhaps pass the GPMessages in and log warnings in there if you need to log exceptions)

					try
					{
						FeatureData data = new FeatureData(feature.OID, feature.ShapeCopy);
						outputFeatures.Add(Convert.ToInt64(feature.get_Value(linesIDFieldIndex)), data);
					}
					catch
					{
					}

					if ((totalDone % cancelCheckInterval) == 0)
					{
						// check for user cancel

						if (!trackcancel.Continue())
							throw (new COMException("Function cancelled."));
					}
				}

				// finished? set up for next batch

				totalDone += numFeaturesPerQuery;

				totalRemaining = numToFetch - totalDone;
				if (totalRemaining > 0)
				{
					if (numFeaturesPerQuery > totalRemaining)
						numFeaturesPerQuery = totalRemaining;
				}
			}

			return outputFeatures;
		}

		public static void CleanUpSignpostFeatureValues(IFeatureBuffer featureBuffer, int lastValidBranchNum, int lastValidTowardNum,
														int[] outBranchXFI, int[] outBranchXDirFI, int[] outBranchXLngFI,
														int[] outTowardXFI, int[] outTowardXLngFI)
		{
			// set unused sequence number values to null (our row buffer may still
			// have junk at the end)

			for (int i = lastValidBranchNum + 1; i < SignpostUtilities.MaxBranchCount; i++)
			{
				featureBuffer.set_Value(outBranchXFI[i], null);
				featureBuffer.set_Value(outBranchXDirFI[i], null);
				featureBuffer.set_Value(outBranchXLngFI[i], null);
			}

			for (int i = lastValidTowardNum + 1; i < SignpostUtilities.MaxBranchCount; i++)
			{
				featureBuffer.set_Value(outTowardXFI[i], null);
				featureBuffer.set_Value(outTowardXLngFI[i], null);
			}
		}
	}
}

[Visual Basic .NET]

SignpostUtilities.vb

Imports System
Imports System.Runtime.InteropServices
Imports System.Collections

Imports ESRI.ArcGIS.esriSystem
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.Geoprocessing
Imports ESRI.ArcGIS.Geometry

Namespace GPImportSignpostFunctions
	Public Class SignpostUtilities
		Public Shared ReadOnly MaxBranchCount As Integer = 5

		Public Structure FeatureData
			Public Sub New(ByVal ID As Integer, ByVal geom As IGeometry)
				OID = ID
				feature = geom
			End Sub
			Public OID As Integer
			Public feature As IGeometry
		End Structure

		Public Sub New()
		End Sub

		Public Shared Function CreateSignsFeatureClass(ByVal linesFeatureClass As IFeatureClass, ByVal name As String) As IFeatureClass
			' Locations are all relative to the location of the reference lines.
			' For Geodatabase, signs feature class is at the same location and the streets table
			' is at the level of the containing feature dataset.
			' For shapefile, both are at the same location as the reference lines.

			' start with the initial set of required fields for a feature class

			Dim fcDescription As IFeatureClassDescription = New FeatureClassDescription
			Dim ocDescription As IObjectClassDescription = CType(fcDescription, IObjectClassDescription)
			Dim outFields As IFieldsEdit = CType(ocDescription.RequiredFields, IFieldsEdit)

            ' make the shape field to be of type polyline with the same spatial reference as the reference lines

            Dim shapeField As IField = outFields.Field(outFields.FindField(fcDescription.ShapeFieldName))
            Dim geomDefEdit As IGeometryDefEdit = CType(shapeField.GeometryDef, IGeometryDefEdit)
            geomDefEdit.GeometryType_2 = esriGeometryType.esriGeometryPolyline
            geomDefEdit.SpatialReference_2 = CType(linesFeatureClass, IGeoDataset).SpatialReference

            ' add the other fields to the feature class

			Dim field As IFieldEdit = New Field
			field.Name_2 = "ExitName"
			field.Type_2 = esriFieldType.esriFieldTypeString
			field.Length_2 = 10
			outFields.AddField(field)

			Dim currentNumber As String

			For i As Integer = 0 To MaxBranchCount - 1
				currentNumber = Convert.ToString(i)

				field = New Field
				field.Name_2 = "Branch" + currentNumber
				field.Type_2 = esriFieldType.esriFieldTypeString
				field.Length_2 = 75
				outFields.AddField(field)

				field = New Field
				field.Name_2 = "Branch" + currentNumber + "Dir"
				field.Type_2 = esriFieldType.esriFieldTypeString
				field.Length_2 = 5
				outFields.AddField(field)

				field = New Field
				field.Name_2 = "Branch" + currentNumber + "Lng"
				field.Type_2 = esriFieldType.esriFieldTypeString
				field.Length_2 = 2
				outFields.AddField(field)

				field = New Field
				field.Name_2 = "Toward" + currentNumber
				field.Type_2 = esriFieldType.esriFieldTypeString
				field.Length_2 = 75
				outFields.AddField(field)

				field = New Field
				field.Name_2 = "Toward" + currentNumber + "Lng"
				field.Type_2 = esriFieldType.esriFieldTypeString
				field.Length_2 = 2
				outFields.AddField(field)
			Next i

			' make the feature class

			Dim featureDataset As IFeatureDataset = linesFeatureClass.FeatureDataset
			Dim workspace As IWorkspace = CType(linesFeatureClass, IDataset).Workspace

			If Not featureDataset Is Nothing Then
                Return featureDataset.CreateFeatureClass(name, outFields, ocDescription.InstanceCLSID, ocDescription.ClassExtensionCLSID, _
                                                         esriFeatureType.esriFTSimple, fcDescription.ShapeFieldName, "")
			ElseIf TypeOf workspace Is IFeatureWorkspace Then
                Return CType(workspace, IFeatureWorkspace).CreateFeatureClass(name, outFields, ocDescription.InstanceCLSID, ocDescription.ClassExtensionCLSID, _
                                                                              esriFeatureType.esriFTSimple, fcDescription.ShapeFieldName, "")
			Else
				Return Nothing	 ' not expected
			End If
		End Function

		Public Shared Function CreateSignsDetailTable(ByVal linesFeatureClass As IFeatureClass, ByVal name As String) As ITable
			' Locations are all relative to the location of the reference lines.
			' For Geodatabase, signs feature class is at the same location and the streets table
			' is at the level of the containing feature dataset.
			' For shapefile, both are at the same location as the reference lines.

			' start with the initial set of required fields for a table

			Dim ocDescription As IObjectClassDescription = New ObjectClassDescription
			Dim outFields As IFieldsEdit = CType(ocDescription.RequiredFields, IFieldsEdit)

			' add the SignpostID field to the table

			Dim field As IFieldEdit = New Field
			field.Name_2 = "SignpostID"
			field.Type_2 = esriFieldType.esriFieldTypeInteger
			outFields.AddField(field)

			' add the other fields to the table

			field = New Field
			field.Name_2 = "Sequence"
			field.Type_2 = esriFieldType.esriFieldTypeInteger
			outFields.AddField(field)

			field = New Field
			field.Name_2 = "EdgeFCID"
			field.Type_2 = esriFieldType.esriFieldTypeInteger
			outFields.AddField(field)

			field = New Field
			field.Name_2 = "EdgeFID"
			field.Type_2 = esriFieldType.esriFieldTypeInteger
			outFields.AddField(field)

			field = New Field
			field.Name_2 = "EdgeFrmPos"
			field.Type_2 = esriFieldType.esriFieldTypeDouble
			outFields.AddField(field)

			field = New Field
			field.Name_2 = "EdgeToPos"
			field.Type_2 = esriFieldType.esriFieldTypeDouble
			outFields.AddField(field)

			' make the table

			Dim featureDataset As IFeatureDataset = linesFeatureClass.FeatureDataset
			Dim workspace As IWorkspace = CType(linesFeatureClass, IDataset).Workspace

			If Not featureDataset Is Nothing Then
				' up a level
				Dim createWS As IFeatureWorkspace = CType(featureDataset.Workspace, IFeatureWorkspace)
				Return createWS.CreateTable(name, outFields, ocDescription.InstanceCLSID, ocDescription.ClassExtensionCLSID, "")
			ElseIf TypeOf workspace Is IFeatureWorkspace Then
				Return CType(workspace, IFeatureWorkspace).CreateTable(name, outFields, ocDescription.InstanceCLSID, ocDescription.ClassExtensionCLSID, "")
			Else
				Return Nothing	 ' not expected        
			End If
		End Function

		Public Shared Function FillFeatureCache(ByVal inputSignsTable As ITable, ByVal inFromIDFI As Integer, ByVal inToIDFI As Integer, ByVal inputLineFeatures As IFeatureClass, ByVal linesIDFieldName As String, ByVal trackcancel As ITrackCancel) As Hashtable
			' make and fill a SortedList from the IDs referenced in the table

			' for MultiNet data, there is only one ID field, so its index will be 
			' passed in as inFromIDFI, while -1 will be passed in to inToIDFI.

			Dim IDs As SortedList = New System.Collections.SortedList()

			Dim inCursor As ICursor = inputSignsTable.Search(Nothing, True)
			Dim row As IRow

			Dim fromID As Long, toID As Long
			Dim exists As Boolean
			Dim cancelCheckInterval As Integer = 100

			row = inCursor.NextRow()
			If inToIDFI = -1 Then
				While Not row Is Nothing
					fromID = Convert.ToInt64(row.Value(inFromIDFI))

					exists = IDs.Contains(fromID)
					If Not exists Then
						IDs.Add(fromID, fromID)
					End If

					row = inCursor.NextRow()
				End While
			Else
				While Not row Is Nothing
					fromID = Convert.ToInt64(row.Value(inFromIDFI))
					toID = Convert.ToInt64(row.Value(inToIDFI))

					exists = IDs.Contains(fromID)
					If Not exists Then
						IDs.Add(fromID, fromID)
					End If

					exists = IDs.Contains(toID)
					If Not exists Then
						IDs.Add(toID, toID)
					End If

					row = inCursor.NextRow()
				End While
			End If

			' make the query filter for fetching features

			Dim queryFilter As IQueryFilter = New QueryFilter
			queryFilter.SubFields = "*"

			' Now fetch batches of features

			Dim currID As Long
			Dim numFeaturesPerQuery As Integer = 200
			Dim numToFetch As Integer = IDs.Count
			Dim totalRemaining As Integer, totalDone As Integer = 0

			Dim linesIDFieldIndex As Integer = inputLineFeatures.FindField(linesIDFieldName)

			Dim outputFeatures As Hashtable = New System.Collections.Hashtable(CType(numToFetch, Integer))

			If numFeaturesPerQuery > numToFetch Then
				numFeaturesPerQuery = numToFetch
			End If

			While totalDone < numToFetch
				' Populate the QueryDef Where clause IN() statement for the current batch of features.
				' This is going to be very slow unless linesIDFieldName is indexed and this is why
				' we added a warning to the GP message window if this is the case.  If you cannot
				' index linesIDFieldName, then this code would run faster scanning the whole feature
				' class looking for the records we need (no Where clause).

				Dim whereClause As String = linesIDFieldName + " IN("

				For i As Integer = 0 To numFeaturesPerQuery - 1
					currID = Convert.ToInt64(IDs.GetByIndex(totalDone + i))
					whereClause += Convert.ToString(currID)
					If i <> (numFeaturesPerQuery - 1) Then
						whereClause += ","
					Else
						whereClause += ")"
					End If
				Next i

				queryFilter.WhereClause = whereClause

				' select the features

				Dim inputFeatureCursor As IFeatureCursor = inputLineFeatures.Search(queryFilter, False)

				' get the features

				Dim feature As IFeature

				feature = inputFeatureCursor.NextFeature()
				While Not feature Is Nothing
					' keep a copy of the OID and shape of feature - skip records that cause errors
					' (perhaps pass the GPMessages in and log warnings in there if you need to log exceptions)

					Try
						Dim data As FeatureData = New FeatureData(feature.OID, feature.ShapeCopy)
						outputFeatures.Add(Convert.ToInt64(feature.Value(linesIDFieldIndex)), data)
					Catch
					End Try

					If (totalDone Mod cancelCheckInterval) = 0 Then
						' check for user cancel

						If Not trackcancel.Continue() Then
							Throw (New COMException("Function cancelled."))
						End If
					End If

					feature = inputFeatureCursor.NextFeature()
				End While

				' finished? set up for next batch

				totalDone += numFeaturesPerQuery

				totalRemaining = numToFetch - totalDone
				If totalRemaining > 0 Then
					If numFeaturesPerQuery > totalRemaining Then
						numFeaturesPerQuery = totalRemaining
					End If
				End If
			End While

			Return outputFeatures
		End Function

		Public Shared Sub CleanUpSignpostFeatureValues(ByVal featureBuffer As IFeatureBuffer, ByVal lastValidBranchNum As Integer, ByVal lastValidTowardNum As Integer, _
					  ByVal outBranchXFI As Integer(), ByVal outBranchXDirFI As Integer(), ByVal outBranchXLngFI As Integer(), _
					  ByVal outTowardXFI As Integer(), ByVal outTowardXLngFI As Integer())

			' set unused sequence number values to null (our row buffer may still
			' have junk at the end)

			For i As Integer = lastValidBranchNum + 1 To SignpostUtilities.MaxBranchCount - 1
				featureBuffer.Value(outBranchXFI(i)) = Nothing
				featureBuffer.Value(outBranchXDirFI(i)) = Nothing
				featureBuffer.Value(outBranchXLngFI(i)) = Nothing
			Next i

			For i As Integer = lastValidTowardNum + 1 To SignpostUtilities.MaxBranchCount - 1
				featureBuffer.Value(outTowardXFI(i)) = Nothing
				featureBuffer.Value(outTowardXLngFI(i)) = Nothing
			Next i
		End Sub
	End Class
End Namespace