ArcObjects Library Reference  

ImportMultiNetSignsFunction

About the Import signposts Sample

[C#]

ImportMultiNetSignsFunction.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;
using ESRI.ArcGIS.Carto;

namespace GPImportSignpostFunctions
{
	/// <summary>
	/// Summary description for ImportNavStreetsSignsFunction.
	/// </summary>
	///

	[Guid("267C4FBF-E89A-49b7-9727-FFFB5594733E")]
	[ClassInterface(ClassInterfaceType.None)]
	[ProgId("GPImportSignpostFunctions.ImportMultiNetSignsFunction")]

	class ImportMultiNetSignsFunction : IGPFunction
	{
		#region Constants
		// parameter index constants
		private const int InputSITable = 0;
		private const int InputSPTable = 1;
		private const int ReferenceLineFeatures = 2;
		private const int OutFeatureClassName = 3;
		private const int OutStreetsTableName = 4;

		// field names and types
		private static readonly string[] SIFieldNames = new string[] 
                                        { "ID", "SEQNR", "DESTSEQ", "INFOTYP", "RNPART",
                                          "TXTCONT", "TXTCONTLC", "CONTYP", "AMBIG" };
		private static readonly esriFieldType[] SIFieldTypes = new esriFieldType[]
                                        { esriFieldType.esriFieldTypeDouble,
                                          esriFieldType.esriFieldTypeInteger,
                                          esriFieldType.esriFieldTypeInteger,
                                          esriFieldType.esriFieldTypeString,
                                          esriFieldType.esriFieldTypeSmallInteger,
                                          esriFieldType.esriFieldTypeString,
                                          esriFieldType.esriFieldTypeString,
                                          esriFieldType.esriFieldTypeSmallInteger,
                                          esriFieldType.esriFieldTypeSmallInteger};
		private static readonly string[] SPFieldNames = new string[] { "ID", "SEQNR", "TRPELID", "TRPELTYP" };
		private static readonly esriFieldType[] SPFieldTypes = new esriFieldType[]
                                        { esriFieldType.esriFieldTypeDouble,
                                          esriFieldType.esriFieldTypeInteger,
                                          esriFieldType.esriFieldTypeDouble,
                                          esriFieldType.esriFieldTypeSmallInteger};
		private static readonly string LinesIDFieldName = "ID";
		private static readonly esriFieldType LinesIDFieldType = esriFieldType.esriFieldTypeDouble;
		#endregion

		private IArray m_parameters;

		public ImportMultiNetSignsFunction()
		{
		}
		#region IGPFunction Members

		public IArray ParameterInfo
		{
			// create and return the parameters for this function:
			// 1 - Input Sign Information Table
			// 2 - Input Sign Path Table
			// 3 - Input Street Features
			// 4 - Output Signpost Feature Class Name
			// 5 - Output Signpost Streets Table Name

			get
			{
				IArray paramArray = new ArrayClass();

				// 1 - input_sign_information_table

				IGPParameterEdit paramEdit = new GPParameterClass();
				paramEdit.DataType = new DETableTypeClass() as IGPDataType;
				paramEdit.Value = new DETableClass() as IGPValue;
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput;
				paramEdit.DisplayName = "Input Sign Information Table";
				paramEdit.Enabled = true;
				paramEdit.Name = "input_sign_information_table";
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired;

				paramArray.Add(paramEdit as object);

				// 2 - input_sign_path_table

				paramEdit = new GPParameterClass();
				paramEdit.DataType = new DETableTypeClass() as IGPDataType;
				paramEdit.Value = new DETableClass() as IGPValue;
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput;
				paramEdit.DisplayName = "Input Sign Path Table";
				paramEdit.Enabled = true;
				paramEdit.Name = "input_sign_path_table";
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired;

				paramArray.Add(paramEdit as object);

				// 3 - reference_street_features

				paramEdit = new GPParameterClass();
				paramEdit.DataType = new DEFeatureClassTypeClass() as IGPDataType;
				paramEdit.Value = new DEFeatureClass() as IGPValue;
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput;
				paramEdit.DisplayName = "Input Street Features";
				paramEdit.Enabled = true;
				paramEdit.Name = "reference_street_features";
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired;

				IGPFeatureClassDomain lineFeatureClassDomain = new GPFeatureClassDomainClass();
				lineFeatureClassDomain.AddType(esriGeometryType.esriGeometryLine);
				lineFeatureClassDomain.AddType(esriGeometryType.esriGeometryPolyline);

				paramEdit.Domain = lineFeatureClassDomain as IGPDomain;

				paramArray.Add(paramEdit as object);

				// 4 - out_feature_class_name

				paramEdit = new GPParameterClass();
				paramEdit.DataType = new GPStringTypeClass() as IGPDataType;
				IGPString stringVal = new GPStringClass();
				stringVal.Value = "Signpost";
				paramEdit.Value = stringVal as IGPValue;
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput;
				paramEdit.DisplayName = "Output Signpost Feature Class Name";
				paramEdit.Enabled = true;
				paramEdit.Name = "out_feature_class_name";
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired;

				paramArray.Add(paramEdit as object);

				// 5 - out_table_name

				paramEdit = new GPParameterClass();
				paramEdit.DataType = new GPStringTypeClass() as IGPDataType;
				stringVal = new GPStringClass();
				stringVal.Value = "SignpostSt";
				paramEdit.Value = stringVal as IGPValue;
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput;
				paramEdit.DisplayName = "Output Signpost Streets Table Name";
				paramEdit.Enabled = true;
				paramEdit.Name = "out_streets_table_name";
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired;

				paramArray.Add(paramEdit as object);

				// TODO: add two derived output parameters for chaining this function
				//       in models

				return paramArray;
			}
		}

		public IGPMessages Validate(IArray paramvalues, bool updateValues, IGPEnvironmentManager envMgr)
		{

			// Create the GPUtilities Object

			IGPUtilities gpUtils = new GPUtilitiesClass();

			// Initialize a copy of our parameters

			if (m_parameters == null) m_parameters = ParameterInfo;

			// Call InternalValidate to check for required parameters

			IGPMessages validateMessages = gpUtils.InternalValidate(m_parameters, paramvalues, updateValues, true, envMgr);

			// Verify chosen input SI table has the expected fields

			IGPParameter gpParam = paramvalues.get_Element(InputSITable) as IGPParameter;
			IGPValue tableValue = gpUtils.UnpackGPValue(gpParam);

			// CheckForTableFields will report errors by modifying the relevant GPMessage

			if (!tableValue.IsEmpty())
			{
				IDETable inputTable = gpUtils.DecodeDETable(tableValue);
				CheckForTableFields(inputTable, SIFieldNames, SIFieldTypes, validateMessages.GetMessage(InputSITable));
			}

			// Verify chosen input SP table has the expected fields

			gpParam = paramvalues.get_Element(InputSPTable) as IGPParameter;
			tableValue = gpUtils.UnpackGPValue(gpParam);

			// CheckForTableFields will report errors by modifying the relevant GPMessage

			if (!tableValue.IsEmpty())
			{
				IDETable inputTable = gpUtils.DecodeDETable(tableValue);
				CheckForTableFields(inputTable, SPFieldNames, SPFieldTypes, validateMessages.GetMessage(InputSPTable));
			}

			// Verify chosen reference_line_features has expected id field

			gpParam = paramvalues.get_Element(ReferenceLineFeatures) as IGPParameter;
			IGPValue featureClassValue = gpUtils.UnpackGPValue(gpParam);

			if (!featureClassValue.IsEmpty())
			{
				IDETable inputTable = gpUtils.DecodeDETable(featureClassValue);
				CheckForLinesIDField(inputTable, validateMessages.GetMessage(ReferenceLineFeatures));
			}

			return validateMessages;
		}

		public void Execute(IArray paramvalues, ITrackCancel trackcancel,
							IGPEnvironmentManager envMgr, IGPMessages messages)
		{
			try
			{
				#region Validate our values, initialize utilities
				IGPMessages validateMessages = Validate(paramvalues, false, envMgr);
				if ((validateMessages as IGPMessage).IsError())
				{
					messages.AddError(1, "Validate failed");
					return;
				}

				IGPUtilities gpUtils = new GPUtilitiesClass();
				#endregion

				#region Open input datasets (unpack values)

				ITable inputSITable;
				ITable inputSPTable;
				IFeatureClass inputLineFeatures;

				IGPParameter gpParam = paramvalues.get_Element(InputSITable) as IGPParameter;
				IGPValue inputSITableValue = gpUtils.UnpackGPValue(gpParam);
				IDataset dataset = gpUtils.OpenDataset(inputSITableValue);

				if (dataset != null)
					inputSITable = dataset as ITable;
				else
				{
					messages.AddError(1, "Could not open input sign information table.");
					return;
				}

				gpParam = paramvalues.get_Element(InputSPTable) as IGPParameter;
				IGPValue inputSPTableValue = gpUtils.UnpackGPValue(gpParam);
				dataset = gpUtils.OpenDataset(inputSPTableValue);

				if (dataset != null)
					inputSPTable = dataset as ITable;
				else
				{
					messages.AddError(1, "Could not open input sign path table.");
					return;
				}

				gpParam = paramvalues.get_Element(ReferenceLineFeatures) as IGPParameter;
				IGPValue inputFeaturesValue = gpUtils.UnpackGPValue(gpParam);
				dataset = gpUtils.OpenDataset(inputFeaturesValue);

				if (dataset != null)
					inputLineFeatures = dataset as IFeatureClass;
				else
				{
					messages.AddError(1, "Could not open input line features.");
					return;
				}
				#endregion

				#region Check for index
				// check if streets table is indexed by ID and add a GPWarning message if not

				IEnumIndex indexEnum = inputLineFeatures.Indexes.FindIndexesByFieldName(LinesIDFieldName);
				indexEnum.Reset();
				IIndex index;
				while ((index = indexEnum.Next()) != null)
				{
					if (index.Fields.FieldCount != 1)
						continue;
					else
						break;
				}

				if (index == null)
					messages.AddWarning("Warning: " + LinesIDFieldName + " is not indexed.");
				#endregion

				// TODO: check if output exists and raise error or delete depending
				//       on overwrite outputs geoprocessing environment info

				#region Create Output datasets

				gpParam = paramvalues.get_Element(OutFeatureClassName) as IGPParameter;
				IGPValue outputNameValue = gpUtils.UnpackGPValue(gpParam);
				string outputName = (outputNameValue as IGPString).Value;

				IFeatureClass outputSignsFeatureClass = SignpostUtilities.CreateSignsFeatureClass(inputLineFeatures, outputName);

				gpParam = paramvalues.get_Element(OutStreetsTableName) as IGPParameter;
				outputNameValue = gpUtils.UnpackGPValue(gpParam);
				outputName = (outputNameValue as IGPString).Value;

				ITable outputSignDetailTable = SignpostUtilities.CreateSignsDetailTable(inputLineFeatures, outputName);
				#endregion

				#region Populate data

				PopulateData(inputSITable, inputSPTable, inputLineFeatures, outputSignsFeatureClass, outputSignDetailTable, messages, trackcancel);
				#endregion
			}
			catch (COMException e)
			{
				messages.AddError(1, e.Message);
			}
		}

		public string DisplayName
		{
			get
			{
				return "Import MultiNet Signs";
			}
		}

		public string MetadataFile
		{
			get
			{
				return "ImportMultiNetSignsHelp.xml";
			}
		}

		public IName FullName
		{
			get
			{
				IGPFunctionFactory functionFactory = new SignpostGPFunctionFactory();
				return functionFactory.GetFunctionName(this.Name) as IName;
			}
		}

		public bool IsLicensed()
		{
			return true;
		}

		public UID DialogCLSID
		{
			get
			{
				return null;
			}
		}

		public string Name
		{
			get
			{
				return "ImportMultiNetSigns";
			}
		}

		public int HelpContext
		{
			get
			{
				return 0;
			}
		}

		public string HelpFile
		{
			get
			{
				return null;
			}
		}

		public object GetRenderer(IGPParameter gpParam)
		{
			return null;
		}
		#endregion

		private bool CheckForTableFields(IDETable inputTable, string[] fieldNames, esriFieldType[] fieldTypes,
										 IGPMessage gpMessage)
		{
			IFields fields = inputTable.Fields;
			int fieldIndex;

			for (int i = 0; i < fieldNames.Length - 1; i++)
			{
				fieldIndex = fields.FindField(fieldNames[i]);
				if (fieldIndex == -1)
				{
					gpMessage.Type = esriGPMessageType.esriGPMessageTypeError;
					gpMessage.Description = "Field named " + fieldNames[i] + " not found.";
					return false;
				}

				if (fields.get_Field(fieldIndex).Type != fieldTypes[i])
				{
					gpMessage.Type = esriGPMessageType.esriGPMessageTypeError;
					gpMessage.Description = "Field named " + fieldNames[i] + " is not the expected type.";
					return false;
				}
			}
			return true;
		}

		private bool CheckForLinesIDField(IDETable pInputTable, IGPMessage pMessage)
		{
			IFields fields = pInputTable.Fields;
			int fieldIndex = fields.FindField(LinesIDFieldName);
			if (fieldIndex == -1)
			{
				pMessage.Type = esriGPMessageType.esriGPMessageTypeError;
				pMessage.Description = "Field named " + LinesIDFieldName + " not found.";
				return false;
			}

			if (fields.get_Field(fieldIndex).Type != LinesIDFieldType)
			{
				pMessage.Type = esriGPMessageType.esriGPMessageTypeError;
				pMessage.Description = "Field named " + LinesIDFieldName + " is not the expected type.";
				return false;
			}

			return true;
		}

		private void PopulateData(ITable inputSignInformationTable, ITable inputSignPathTable, IFeatureClass inputLineFeatures,
								  IFeatureClass outputSignFeatures, ITable outputSignDetailTable,
								  IGPMessages messages, ITrackCancel trackcancel)
		{
			#region Find fields
			//(Validate checked that these exist)
			IFields inputSITableFields = inputSignInformationTable.Fields;
			int inSiIdFI = inputSITableFields.FindField("ID");
			int inSiInfoTypFI = inputSITableFields.FindField("INFOTYP");
			int inSiTxtContFI = inputSITableFields.FindField("TXTCONT");
			int inSiConTypFI = inputSITableFields.FindField("CONTYP");
			IFields inputSPTableFields = inputSignPathTable.Fields;
			int inSpIdFI = inputSPTableFields.FindField("ID");
			int inSpSeqNrFI = inputSPTableFields.FindField("SEQNR");
			int inSpTrpElIdFI = inputSPTableFields.FindField("TRPELID");
			int inSpTrpElTypFI = inputSPTableFields.FindField("TRPELTYP");

			// Find output fields (we just made these)

			IFields outputSignFeatureFields = outputSignFeatures.Fields;
			int outExitNameFI = outputSignFeatureFields.FindField("ExitName");

			int[] outBranchXFI = new int[SignpostUtilities.MaxBranchCount];
			int[] outBranchXDirFI = new int[SignpostUtilities.MaxBranchCount];
			int[] outBranchXLngFI = new int[SignpostUtilities.MaxBranchCount];
			int[] outTowardXFI = new int[SignpostUtilities.MaxBranchCount];
			int[] outTowardXLngFI = new int[SignpostUtilities.MaxBranchCount];

			string indexString;

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

				outBranchXFI[i] = outputSignFeatureFields.FindField("Branch" + indexString);
				outBranchXDirFI[i] = outputSignFeatureFields.FindField("Branch" + indexString + "Dir");
				outBranchXLngFI[i] = outputSignFeatureFields.FindField("Branch" + indexString + "Lng");
				outTowardXFI[i] = outputSignFeatureFields.FindField("Toward" + indexString);
				outTowardXLngFI[i] = outputSignFeatureFields.FindField("Toward" + indexString + "Lng");
			}

			IFields outputTableFields = outputSignDetailTable.Fields;
			int outTblSignpostIDFI = outputTableFields.FindField("SignpostID");
			int outTblSequenceFI = outputTableFields.FindField("Sequence");
			int outTblEdgeFCIDFI = outputTableFields.FindField("EdgeFCID");
			int outTblEdgeFIDFI = outputTableFields.FindField("EdgeFID");
			int outTblEdgeFrmPosFI = outputTableFields.FindField("EdgeFrmPos");
			int outTblEdgeToPosFI = outputTableFields.FindField("EdgeToPos");

			// Find ID fields on referenced lines

			int inLinesOIDFI = inputLineFeatures.FindField(inputLineFeatures.OIDFieldName);
			int inLinesUserIDFI = inputLineFeatures.FindField(LinesIDFieldName);
			int inLinesShapeFI = inputLineFeatures.FindField(inputLineFeatures.ShapeFieldName);

			#endregion

			// Fetch all line features referenced by the input signs table.  We do the
			// "join" this hard way to support all data sources in the sample. 
			// Also, for large numbers of sign records, this strategy of fetching all
			// related features and holding them in RAM could be a problem.  To fix
			// this, one could process the input sign records in batches.

			System.Collections.Hashtable lineFeaturesList = SignpostUtilities.FillFeatureCache(inputSignPathTable, inSpTrpElIdFI, -1, inputLineFeatures, LinesIDFieldName, trackcancel);

			// Create output feature/row buffers

			IFeatureBuffer featureBuffer = outputSignFeatures.CreateFeatureBuffer();
			IFeature feature = featureBuffer as IFeature;
			IRowBuffer featureRowBuffer = featureBuffer as IRowBuffer;

			IRowBuffer tableBuffer = outputSignDetailTable.CreateRowBuffer();
			IRow row = tableBuffer as IRow;
			IRowBuffer tableRowBuffer = tableBuffer as IRowBuffer;

			// Create insert cursors.

			IFeatureCursor featureInsertCursor = outputSignFeatures.Insert(true);
			ICursor tableInsertCursor = outputSignDetailTable.Insert(true);

			// Create input cursors for the sign tables we are importing

			ITableSort spTableSort = new TableSortClass();
			spTableSort.Fields = "ID, SEQNR";
			spTableSort.set_Ascending("ID", true);
			spTableSort.set_Ascending("SEQNR", true);
			spTableSort.QueryFilter = null;
			spTableSort.Table = inputSignPathTable;
			spTableSort.Sort(null);
			ICursor spInputCursor = spTableSort.Rows;

			ITableSort siTableSort = new TableSortClass();
			siTableSort.Fields = "ID, SEQNR, DESTSEQ, RNPART";
			siTableSort.set_Ascending("ID", true);
			siTableSort.set_Ascending("SEQNR", true);
			siTableSort.set_Ascending("DESTSEQ", true);
			siTableSort.set_Ascending("RNPART", true);
			siTableSort.QueryFilter = null;
			siTableSort.Table = inputSignInformationTable;
			siTableSort.Sort(null);
			ICursor siInputCursor = siTableSort.Rows;

			IRow inputSpTableRow;
			IRow inputSiTableRow;
			long currentID = -1, loopSpID, loopSiID;

			int numOutput = 0;
			int numInput = 0;
			bool fetchFeatureDataSucceeded;
			ArrayList idVals = new System.Collections.ArrayList(2);
			ArrayList edgesData = new System.Collections.ArrayList(2);
			ArrayList reverseEdge = new System.Collections.ArrayList(2);
			SignpostUtilities.FeatureData currentFeatureData = new SignpostUtilities.FeatureData(-1, null);
			ICurve earlierEdgeCurve, laterEdgeCurve;
			IPoint earlierEdgeStart, earlierEdgeEnd;
			IPoint laterEdgeStart, laterEdgeEnd;

			int nextBranchNum = -1, nextTowardNum = -1;
			string infoTypText, txtContText;
			string directionalAsText = "";
			string towardText;
			int conTypVal;

			int refLinesFCID = inputLineFeatures.ObjectClassID;
			IGeometry outputSignGeometry;
			object newOID;

			inputSpTableRow = spInputCursor.NextRow();
			inputSiTableRow = siInputCursor.NextRow();
			while (inputSpTableRow != null && inputSiTableRow != null)
			{
				currentID = Convert.ToInt64(inputSpTableRow.get_Value(inSpIdFI));

				// fetch the edge ID values from the SP table for the current sign ID

				idVals.Clear();
				while (true)
				{
					idVals.Add(Convert.ToInt64(inputSpTableRow.get_Value(inSpTrpElIdFI)));

					inputSpTableRow = spInputCursor.NextRow();
					if (inputSpTableRow == null)
						break;    // we've reached the end of the SP table

					loopSpID = Convert.ToInt64(inputSpTableRow.get_Value(inSpIdFI));
					if (loopSpID != currentID)
						break;    // we're now on a new ID value					
				}

				numInput++;

				// fetch the FeatureData for each of these edges

				edgesData.Clear();
				fetchFeatureDataSucceeded = true;
				foreach (long currentIDVal in idVals)
				{
					try
					{
						currentFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[currentIDVal];
						edgesData.Add(currentFeatureData);
					}
					catch
					{
						fetchFeatureDataSucceeded = false;
						if (numInput - numOutput < 100)
						{
							messages.AddWarning("Line feature not found for sign with ID: " +
								Convert.ToString(currentIDVal));
						}
						break;
					}
				}
				if (!fetchFeatureDataSucceeded)
					continue;

				// determine the orientation for each of these edges

				reverseEdge.Clear();
				for (int i = 1; i < edgesData.Count; i++)
				{
					// get the endpoints of the earlier curve
					currentFeatureData = (SignpostUtilities.FeatureData)edgesData[i - 1];
					earlierEdgeCurve = currentFeatureData.feature as ICurve;
					earlierEdgeStart = earlierEdgeCurve.FromPoint;
					earlierEdgeEnd = earlierEdgeCurve.ToPoint;

					// get the endpoints of the later curve
					currentFeatureData = (SignpostUtilities.FeatureData)edgesData[i];
					laterEdgeCurve = currentFeatureData.feature as ICurve;
					laterEdgeStart = laterEdgeCurve.FromPoint;
					laterEdgeEnd = laterEdgeCurve.ToPoint;

					// determine the orientation of the first edge
					// (first edge is reversed if its Start point is coincident with either point of the second edge)
					if (i == 1)
						reverseEdge.Add(EqualPoints(earlierEdgeStart, laterEdgeStart) || EqualPoints(earlierEdgeStart, laterEdgeEnd));

					// determine the orientation of the i'th edge
					// (i'th edge is reversed if its End point is coincident with either point of the previous edge)
					reverseEdge.Add(EqualPoints(laterEdgeEnd, earlierEdgeStart) || EqualPoints(laterEdgeEnd, earlierEdgeEnd));
				}

				// write out the sign geometry to the featureBuffer

				outputSignGeometry = MakeSignGeometry(edgesData, reverseEdge);
				featureBuffer.Shape = outputSignGeometry;

				// fetch the signpost information from the SI table for the current sign ID

				nextBranchNum = 0;
				nextTowardNum = 0;
				featureBuffer.set_Value(outExitNameFI, "");

				while (inputSiTableRow != null)
				{
					loopSiID = Convert.ToInt64(inputSiTableRow.get_Value(inSiIdFI));
					if (loopSiID < currentID)
					{
						inputSiTableRow = siInputCursor.NextRow();
						continue;
					}
					else if (loopSiID > currentID)
					{
						break;    // we're now on a new ID value
					}

					infoTypText = inputSiTableRow.get_Value(inSiInfoTypFI) as string;
					txtContText = inputSiTableRow.get_Value(inSiTxtContFI) as string;
					conTypVal = Convert.ToInt32(inputSiTableRow.get_Value(inSiConTypFI));

					switch (infoTypText)
					{
						case "4E":    // exit number

							featureBuffer.set_Value(outExitNameFI, txtContText);

							break;
						case "9D":    // place name
						case "4I":    // other destination

							// check for schema overflow
							if (nextTowardNum > SignpostUtilities.MaxBranchCount - 1)
							{
								inputSiTableRow = siInputCursor.NextRow();
								continue;
							}

							// set values
							featureBuffer.set_Value(outTowardXFI[nextTowardNum], txtContText);
							featureBuffer.set_Value(outTowardXLngFI[nextTowardNum], "en");

							// get ready for next toward
							nextTowardNum++;

							break;
						case "6T":    // street name
						case "RN":    // route number

							if (conTypVal == 2)    // toward
							{
								// check for schema overflow
								if (nextTowardNum > SignpostUtilities.MaxBranchCount - 1)
								{
									inputSiTableRow = siInputCursor.NextRow();
									continue;
								}

								// set values
								featureBuffer.set_Value(outTowardXFI[nextTowardNum], txtContText);
								featureBuffer.set_Value(outTowardXLngFI[nextTowardNum], "en");

								// get ready for next toward
								nextTowardNum++;
							}
							else    // branch
							{
								// check for schema overflow
								if (nextBranchNum > SignpostUtilities.MaxBranchCount - 1)
								{
									inputSiTableRow = siInputCursor.NextRow();
									continue;
								}

								// set values
								featureBuffer.set_Value(outBranchXFI[nextBranchNum], txtContText);
								featureBuffer.set_Value(outBranchXDirFI[nextBranchNum], "");
								featureBuffer.set_Value(outBranchXLngFI[nextBranchNum], "en");

								// get ready for next branch
								nextBranchNum++;
							}

							break;
						case "7G":    // route directional

							// convert the directional value to a string
							switch (txtContText)
							{
								case "1":
									directionalAsText = "N";
									break;
								case "2":
									directionalAsText = "E";
									break;
								case "3":
									directionalAsText = "S";
									break;
								case "4":
									directionalAsText = "W";
									break;
							}

							if (conTypVal == 2)    // toward
							{
								// check for schema underflow
								if (nextTowardNum == 0)
								{
									inputSiTableRow = siInputCursor.NextRow();
									continue;
								}

								// append directional text to the previous toward text value
								towardText = featureBuffer.get_Value(outTowardXFI[nextTowardNum - 1]) as string;
								towardText = towardText + " " + directionalAsText;
								featureBuffer.set_Value(outTowardXFI[nextTowardNum - 1], towardText);
							}
							else    // branch
							{
								// check for schema underflow
								if (nextBranchNum == 0)
								{
									inputSiTableRow = siInputCursor.NextRow();
									continue;
								}

								// set value of the Dir field on the previous branch
								featureBuffer.set_Value(outBranchXDirFI[nextBranchNum - 1], directionalAsText);
							}

							break;
					}  // switch

					inputSiTableRow = siInputCursor.NextRow();

				}  // each SI table record

				// clean up unused parts of the row and pack toward/branch items

				SignpostUtilities.CleanUpSignpostFeatureValues(featureBuffer, nextBranchNum - 1, nextTowardNum - 1,
															   outBranchXFI, outBranchXDirFI, outBranchXLngFI,
															   outTowardXFI, outTowardXLngFI);

				// save sign feature record

				newOID = featureInsertCursor.InsertFeature(featureBuffer);

				// set streets table values

				tableRowBuffer.set_Value(outTblSignpostIDFI, newOID);
				tableRowBuffer.set_Value(outTblEdgeFCIDFI, refLinesFCID);
				for (int i = 0; i < edgesData.Count; i++)
				{
					currentFeatureData = (SignpostUtilities.FeatureData)edgesData[i];
					tableRowBuffer.set_Value(outTblSequenceFI, i + 1);
					tableRowBuffer.set_Value(outTblEdgeFIDFI, currentFeatureData.OID);
					if ((bool)reverseEdge[i])
					{
						tableRowBuffer.set_Value(outTblEdgeFrmPosFI, 1.0);
						tableRowBuffer.set_Value(outTblEdgeToPosFI, 0.0);
					}
					else
					{
						tableRowBuffer.set_Value(outTblEdgeFrmPosFI, 0.0);
						tableRowBuffer.set_Value(outTblEdgeToPosFI, 1.0);
					}

					// insert detail record

					tableInsertCursor.InsertRow(tableRowBuffer);
				}

				numOutput++;
				if ((numOutput % 100) == 0)
				{
					// check for user cancel

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

			}  // outer while

			// add a summary message

			messages.AddMessage(Convert.ToString(numOutput) + " of " + Convert.ToString(numInput) + " signposts added.");

			return;
		}

		private bool EqualPoints(IPoint p1, IPoint p2)
		{
			return ((p1.X == p2.X) && (p1.Y == p2.Y));
		}

		private IGeometry MakeSignGeometry(ArrayList edgesData, ArrayList reverseEdge)
		{
			ISegmentCollection resultSegments = new PolylineClass();
			SignpostUtilities.FeatureData currentFeatureData = new SignpostUtilities.FeatureData(-1, null);
			ICurve currentCurve, resultCurve;

			for (int i = 0; i < edgesData.Count; i++)
			{
				// fetch the curve and reverse it as needed

				currentFeatureData = (SignpostUtilities.FeatureData)edgesData[i];
				currentCurve = currentFeatureData.feature as ICurve;
				if ((bool)reverseEdge[i])
					currentCurve.ReverseOrientation();

				// trim the first and last geometries so that they only cover 25% of the street feature

				if (i == 0)
					currentCurve.GetSubcurve(0.75, 1.0, true, out resultCurve);
				else if (i == (edgesData.Count - 1))
					currentCurve.GetSubcurve(0.0, 0.25, true, out resultCurve);
				else
					resultCurve = currentCurve;

				// add the resulting geometry to the collection

				resultSegments.AddSegmentCollection(resultCurve as ISegmentCollection);
			}

			return resultSegments as IGeometry;
		}
	}
}

[Visual Basic .NET]

ImportMultiNetSignsFunction.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
Imports ESRI.ArcGIS.Carto

Namespace GPImportSignpostFunctions
	<Guid("F5AB9C9C-8193-40fa-99B2-369545BCAAEE")> _
	<ClassInterface(ClassInterfaceType.None)> _
	<ProgId("GPImportSignpostFunctions.ImportMultiNetSignsFunction")> _
	Public Class ImportMultiNetSignsFunction
		Implements IGPFunction

#Region "Constants"
		' parameter index constants
		Private Const InputSITable As Integer = 0
		Private Const InputSPTable As Integer = 1
		Private Const InputTable As Integer = 0
		Private Const ReferenceLineFeatures As Integer = 2
		Private Const OutFeatureClassName As Integer = 3
		Private Const OutStreetsTableName As Integer = 4

		' field names and types
		Private Shared ReadOnly SIFieldNames() As String = New String() _
		{"ID", "SEQNR", "DESTSEQ", "INFOTYP", "RNPART", "TXTCONT", "TXTCONTLC", "CONTYP", "AMBIG"}

		Private Shared ReadOnly SIFieldTypes() As esriFieldType = New esriFieldType() _
		{esriFieldType.esriFieldTypeDouble, _
		 esriFieldType.esriFieldTypeInteger, _
		 esriFieldType.esriFieldTypeInteger, _
		 esriFieldType.esriFieldTypeString, _
		 esriFieldType.esriFieldTypeSmallInteger, _
		 esriFieldType.esriFieldTypeString, _
		 esriFieldType.esriFieldTypeString, _
		 esriFieldType.esriFieldTypeSmallInteger, _
		 esriFieldType.esriFieldTypeSmallInteger}

		Private Shared ReadOnly SPFieldNames() As String = New String() _
		{"ID", "SEQNR", "TRPELID", "TRPELTYP"}

		Private Shared ReadOnly SPFieldTypes() As esriFieldType = New esriFieldType() _
		{esriFieldType.esriFieldTypeDouble, _
		 esriFieldType.esriFieldTypeInteger, _
		 esriFieldType.esriFieldTypeDouble, _
		 esriFieldType.esriFieldTypeSmallInteger}

		Private Shared ReadOnly LinesIDFieldName As String = "ID"
		Private Shared ReadOnly LinesIDFieldType As esriFieldType = esriFieldType.esriFieldTypeDouble

#End Region

		Private m_parameters As IArray

		Public Sub New()
		End Sub

#Region "IGPFunction Members"

		Public ReadOnly Property ParameterInfo() As IArray Implements IGPFunction.ParameterInfo
			Get
				Dim gpParamArray As IArray = New ESRI.ArcGIS.esriSystem.Array

				' 1 - input_sign_information_table

				Dim paramEdit As IGPParameterEdit = New GPParameter
				paramEdit.DataType = New DETableType
				paramEdit.Value = New DETable
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput
				paramEdit.DisplayName = "Input Sign Information Table"
				paramEdit.Enabled = True
				paramEdit.Name = "input_sign_information_table"
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired

				gpParamArray.Add(paramEdit)

				' 2 - input_sign_path_table

				paramEdit = New GPParameter
				paramEdit.DataType = New DETableType
				paramEdit.Value = New DETable
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput
				paramEdit.DisplayName = "Input Sign Path Table"
				paramEdit.Enabled = True
				paramEdit.Name = "input_sign_path_table"
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired

				gpParamArray.Add(paramEdit)

				' 3 - reference_street_features

				paramEdit = New GPParameter
				paramEdit.DataType = New DEFeatureClassType
				paramEdit.Value = New DEFeatureClass
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput
				paramEdit.DisplayName = "Input Street Features"
				paramEdit.Enabled = True
				paramEdit.Name = "reference_street_features"
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired

				Dim lineFeatureClassDomain As IGPFeatureClassDomain = New GPFeatureClassDomain
				lineFeatureClassDomain.AddType(esriGeometryType.esriGeometryLine)
				lineFeatureClassDomain.AddType(esriGeometryType.esriGeometryPolyline)

				paramEdit.Domain = CType(lineFeatureClassDomain, IGPDomain)

				gpParamArray.Add(paramEdit)

				' 4 - out_feature_class_name

				paramEdit = New GPParameter
				paramEdit.DataType = New GPStringType
				Dim stringVal As IGPString = New GPString
				stringVal.Value = "Signpost"
				paramEdit.Value = CType(stringVal, IGPValue)
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput
				paramEdit.DisplayName = "Output Signpost Feature Class Name"
				paramEdit.Enabled = True
				paramEdit.Name = "out_feature_class_name"
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired

				gpParamArray.Add(paramEdit)

				' 5 - out_table_name

				paramEdit = New GPParameter
				paramEdit.DataType = New GPStringType
				stringVal = New GPString
				stringVal.Value = "SignpostSt"
				paramEdit.Value = CType(stringVal, IGPValue)
				paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput
				paramEdit.DisplayName = "Output Signpost Streets Table Name"
				paramEdit.Enabled = True
				paramEdit.Name = "out_streets_table_name"
				paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired

				gpParamArray.Add(paramEdit)

				' TODO: add two derived output parameters for chaining this function
				'       in models

				Return gpParamArray
			End Get
		End Property

		Public Function Validate(ByVal paramvalues As IArray, ByVal updateValues As Boolean, ByVal envMgr As IGPEnvironmentManager) As IGPMessages Implements IGPFunction.Validate

			' Create the GPUtilities Object

			Dim gpUtils As IGPUtilities = New GPUtilities

			' Initialize a copy of our parameters

			If m_parameters Is Nothing Then
				m_parameters = ParameterInfo
			End If

			' Call InternalValidate to check for required parameters

			Dim validateMessages As IGPMessages = gpUtils.InternalValidate(m_parameters, paramvalues, updateValues, True, envMgr)

			' Verify chosen input SI table has the expected fields

			Dim gpParam As IGPParameter = CType(paramvalues.Element(InputSITable), IGPParameter)
			Dim tableValue As IGPValue = gpUtils.UnpackGPValue(gpParam)

			' CheckForTableFields will report errors by modifying the relevant GPMessage

			If Not tableValue.IsEmpty() Then
				Dim inputDETable As IDETable = gpUtils.DecodeDETable(tableValue)
				CheckForTableFields(inputDETable, SIFieldNames, SIFieldTypes, validateMessages.GetMessage(InputSITable))
			End If

			' Verify chosen input SP table has the expected fields

			gpParam = CType(paramvalues.Element(InputSPTable), IGPParameter)
			tableValue = gpUtils.UnpackGPValue(gpParam)

			' CheckForTableFields will report errors by modifying the relevant GPMessage

			If Not tableValue.IsEmpty() Then
				Dim inputDETable As IDETable = gpUtils.DecodeDETable(tableValue)
				CheckForTableFields(inputDETable, SPFieldNames, SPFieldTypes, validateMessages.GetMessage(InputSPTable))
			End If

			' Verify chosen reference_line_features has expected id field

			gpParam = CType(paramvalues.Element(ReferenceLineFeatures), IGPParameter)
			Dim featureClassValue As IGPValue = gpUtils.UnpackGPValue(gpParam)

			If Not featureClassValue.IsEmpty() Then
				Dim inputDETable As IDETable = gpUtils.DecodeDETable(featureClassValue)
				CheckForLinesIDField(inputDETable, validateMessages.GetMessage(ReferenceLineFeatures))
			End If

			Return validateMessages
		End Function

		Public Sub Execute(ByVal paramvalues As IArray, ByVal trackcancel As ITrackCancel, ByVal envMgr As IGPEnvironmentManager, ByVal messages As IGPMessages) Implements IGPFunction.Execute
			Try
				' VALIDATE OUR VALUES, INITIALIZE UTILITIES
				Dim validateMessages As IGPMessages = Validate(paramvalues, False, envMgr)
				If CType(validateMessages, IGPMessage).IsError() Then
					messages.AddError(1, "Validate failed")
					Return
				End If

				Dim gpUtils As IGPUtilities = New GPUtilities

				' OPEN INPUT DATASETS (UNPACK VALUES)

				Dim inputSITableAsITable As ITable
				Dim inputSPTableAsITable As ITable
				Dim inputLineFeatures As IFeatureClass

				Dim gpParam As IGPParameter = CType(paramvalues.Element(InputSITable), IGPParameter)
				Dim inputSITableValue As IGPValue = gpUtils.UnpackGPValue(gpParam)
				Dim dataset As IDataset = gpUtils.OpenDataset(inputSITableValue)

				If dataset IsNot Nothing Then
					inputSITableAsITable = CType(dataset, ITable)
				Else
					messages.AddError(1, "Could not open input sign information table.")
					Return
				End If

				gpParam = CType(paramvalues.Element(InputSPTable), IGPParameter)
				Dim inputSPTableValue As IGPValue = gpUtils.UnpackGPValue(gpParam)
				dataset = gpUtils.OpenDataset(inputSPTableValue)

				If dataset IsNot Nothing Then
					inputSPTableAsITable = CType(dataset, ITable)
				Else
					messages.AddError(1, "Could not open input sign path table.")
					Return
				End If

				gpParam = CType(paramvalues.Element(ReferenceLineFeatures), IGPParameter)
				Dim inputFeaturesValue As IGPValue = gpUtils.UnpackGPValue(gpParam)
				dataset = gpUtils.OpenDataset(inputFeaturesValue)

				If dataset IsNot Nothing Then
					inputLineFeatures = CType(dataset, IFeatureClass)
				Else
					messages.AddError(1, "Could not open input line features.")
					Return
				End If

				' CHECK FOR INDEX
				' check if streets table is indexed by ID and add a GPWarning message if not

				Dim indexEnum As IEnumIndex = inputLineFeatures.Indexes.FindIndexesByFieldName(LinesIDFieldName)
				indexEnum.Reset()
				Dim index As IIndex = indexEnum.Next()
				While index IsNot Nothing
					If index.Fields.FieldCount <> 1 Then
						Continue While
					Else
						Exit While
					End If
					index = indexEnum.Next()
				End While

				If index Is Nothing Then
					messages.AddWarning("Warning: " + LinesIDFieldName + " is not indexed.")
				End If

				' TODO: check if output exists and raise error or delete depending
				'       on overwrite outputs geoprocessing environment info

				' CREATE OUTPUT DATASETS

				gpParam = CType(paramvalues.Element(OutFeatureClassName), IGPParameter)
				Dim outputNameValue As IGPValue = gpUtils.UnpackGPValue(gpParam)
				Dim outputName As String = CType(outputNameValue, IGPString).Value

				Dim outputSignsFeatureClass As IFeatureClass = SignpostUtilities.CreateSignsFeatureClass(inputLineFeatures, outputName)

				gpParam = CType(paramvalues.Element(OutStreetsTableName), IGPParameter)
				outputNameValue = gpUtils.UnpackGPValue(gpParam)
				outputName = CType(outputNameValue, IGPString).Value

				Dim outputSignDetailTable As ITable = SignpostUtilities.CreateSignsDetailTable(inputLineFeatures, outputName)

				' POPULATE DATA

				PopulateData(inputSITableAsITable, inputSPTableAsITable, inputLineFeatures, outputSignsFeatureClass, outputSignDetailTable, messages, trackcancel)
			Catch e As COMException
				messages.AddError(1, e.Message)
			End Try
		End Sub

		Public ReadOnly Property DisplayName() As String Implements IGPFunction.DisplayName
			Get
				Return "Import MultiNet Signs"
			End Get
		End Property

		Public ReadOnly Property MetadataFile() As String Implements IGPFunction.MetadataFile
			Get
				Return "ImportMultiNetSignsHelp.xml"
			End Get
		End Property

		Public ReadOnly Property FullName() As IName Implements IGPFunction.FullName
			Get
				Dim functionFactory As IGPFunctionFactory = New SignpostGPFunctionFactory
				Return CType(functionFactory.GetFunctionName(Me.Name), IName)
			End Get
		End Property

		Public Function IsLicensed() As Boolean Implements IGPFunction.IsLicensed
			Return True
		End Function

		Public ReadOnly Property DialogCLSID() As UID Implements IGPFunction.DialogCLSID
			Get
				Return Nothing
			End Get
		End Property

		Public ReadOnly Property Name() As String Implements IGPFunction.Name
			Get
				Return "ImportMultiNetSigns"
			End Get
		End Property

		Public ReadOnly Property HelpContext() As Integer Implements IGPFunction.HelpContext
			Get
				Return 0
			End Get
		End Property

		Public ReadOnly Property HelpFile() As String Implements IGPFunction.HelpFile
			Get
				Return Nothing
			End Get
		End Property

		Public Function GetRenderer(ByVal pParam As IGPParameter) As Object Implements IGPFunction.GetRenderer
			Return Nothing
		End Function
#End Region

		Private Function CheckForTableFields(ByVal inputDETable As IDETable, ByVal fieldNames As String(), ByVal fieldTypes As esriFieldType(), _
		   ByVal gpMessage As IGPMessage) As Boolean
			Dim fields As IFields = inputDETable.Fields
			Dim fieldIndex As Integer

			For i As Integer = 0 To fieldNames.Length - 2
				fieldIndex = fields.FindField(fieldNames(i))
				If fieldIndex = -1 Then
					gpMessage.Type = esriGPMessageType.esriGPMessageTypeError
					gpMessage.Description = "Field named " + fieldNames(i) + " not found."
					Return False
				End If

				If fields.Field(fieldIndex).Type <> fieldTypes(i) Then
					gpMessage.Type = esriGPMessageType.esriGPMessageTypeError
					gpMessage.Description = "Field named " + fieldNames(i) + " is not the expected type."
					Return False
				End If
			Next
			Return True
		End Function

		Private Function CheckForLinesIDField(ByVal inputDETable As IDETable, ByVal gpMessage As IGPMessage) As Boolean
			Dim fields As IFields = inputDETable.Fields
			Dim fieldIndex As Integer = fields.FindField(LinesIDFieldName)
			If fieldIndex = -1 Then
				gpMessage.Type = esriGPMessageType.esriGPMessageTypeError
				gpMessage.Description = "Field named " + LinesIDFieldName + " not found."
				Return False
			End If

			If fields.Field(fieldIndex).Type <> LinesIDFieldType Then
				gpMessage.Type = esriGPMessageType.esriGPMessageTypeError
				gpMessage.Description = "Field named " + LinesIDFieldName + " is not the expected type."
				Return False
			End If

			Return True
		End Function

		Private Sub PopulateData(ByVal inputSignInformationTable As ITable, ByVal inputSignPathTable As ITable, ByVal inputLineFeatures As IFeatureClass, _
		 ByVal outputSignFeatures As IFeatureClass, ByVal outputSignDetailTable As ITable, _
		   ByVal messages As IGPMessages, ByVal trackcancel As ITrackCancel)
			'FIND FIELDS
			'(Validate checked that these exist)
			Dim inputSITableFields As IFields = inputSignInformationTable.Fields
			Dim inSiIdFI As Integer = inputSITableFields.FindField("ID")
			Dim inSiInfoTypFI As Integer = inputSITableFields.FindField("INFOTYP")
			Dim inSiTxtContFI As Integer = inputSITableFields.FindField("TXTCONT")
			Dim inSiConTypFI As Integer = inputSITableFields.FindField("CONTYP")
			Dim inputSPTableFields As IFields = inputSignPathTable.Fields
			Dim inSpIdFI As Integer = inputSPTableFields.FindField("ID")
			Dim inSpSeqNrFI As Integer = inputSPTableFields.FindField("SEQNR")
			Dim inSpTrpElIdFI As Integer = inputSPTableFields.FindField("TRPELID")
			Dim inSpTrpElTypFI As Integer = inputSPTableFields.FindField("TRPELTYP")

			' Find output fields (we just made these)

			Dim outputSignFeatureFields As IFields = outputSignFeatures.Fields
			Dim outExitNameFI As Integer = outputSignFeatureFields.FindField("ExitName")

			Dim outBranchXFI() As Integer = New Integer(SignpostUtilities.MaxBranchCount) {}
			Dim outBranchXDirFI() As Integer = New Integer(SignpostUtilities.MaxBranchCount) {}
			Dim outBranchXLngFI() As Integer = New Integer(SignpostUtilities.MaxBranchCount) {}
			Dim outTowardXFI() As Integer = New Integer(SignpostUtilities.MaxBranchCount) {}
			Dim outTowardXLngFI() As Integer = New Integer(SignpostUtilities.MaxBranchCount) {}

			Dim indexString As String

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

				outBranchXFI(i) = outputSignFeatureFields.FindField("Branch" + indexString)
				outBranchXDirFI(i) = outputSignFeatureFields.FindField("Branch" + indexString + "Dir")
				outBranchXLngFI(i) = outputSignFeatureFields.FindField("Branch" + indexString + "Lng")
				outTowardXFI(i) = outputSignFeatureFields.FindField("Toward" + indexString)
				outTowardXLngFI(i) = outputSignFeatureFields.FindField("Toward" + indexString + "Lng")
			Next

			Dim outputTableFields As IFields = outputSignDetailTable.Fields
			Dim outTblSignpostIDFI As Integer = outputTableFields.FindField("SignpostID")
			Dim outTblSequenceFI As Integer = outputTableFields.FindField("Sequence")
			Dim outTblEdgeFCIDFI As Integer = outputTableFields.FindField("EdgeFCID")
			Dim outTblEdgeFIDFI As Integer = outputTableFields.FindField("EdgeFID")
			Dim outTblEdgeFrmPosFI As Integer = outputTableFields.FindField("EdgeFrmPos")
			Dim outTblEdgeToPosFI As Integer = outputTableFields.FindField("EdgeToPos")

			' Find ID fields on referenced lines

			Dim inLinesOIDFI As Integer = inputLineFeatures.FindField(inputLineFeatures.OIDFieldName)
			Dim inLinesUserIDFI As Integer = inputLineFeatures.FindField(LinesIDFieldName)
			Dim inLinesShapeFI As Integer = inputLineFeatures.FindField(inputLineFeatures.ShapeFieldName)


			' Fetch all line features referenced by the input signs table.  We do the
			' "join" this hard way to support all data sources in the sample. 
			' Also, for large numbers of sign records, this strategy of fetching all
			' related features and holding them in RAM could be a problem.  To fix
			' this, one could process the input sign records in batches.

			Dim lineFeaturesList As System.Collections.Hashtable = SignpostUtilities.FillFeatureCache(inputSignPathTable, inSpTrpElIdFI, -1, inputLineFeatures, LinesIDFieldName, trackcancel)

			' Create output feature/row buffers

			Dim featureBuffer As IFeatureBuffer = outputSignFeatures.CreateFeatureBuffer()
			Dim feature As IFeature = CType(featureBuffer, IFeature)
			Dim featureRowBuffer As IRowBuffer = featureBuffer

			Dim tableBuffer As IRowBuffer = outputSignDetailTable.CreateRowBuffer()
			Dim row As IRow = CType(tableBuffer, IRow)
			Dim tableRowBuffer As IRowBuffer = tableBuffer

			' Create insert cursors.

			Dim featureInsertCursor As IFeatureCursor = outputSignFeatures.Insert(True)
			Dim tableInsertCursor As ICursor = outputSignDetailTable.Insert(True)

			' Create input cursors for the sign tables we are importing

			Dim spTableSort As ITableSort = New TableSort()
			spTableSort.Fields = "ID, SEQNR"
			spTableSort.Ascending("ID") = True
			spTableSort.Ascending("SEQNR") = True
			spTableSort.QueryFilter = Nothing
			spTableSort.Table = inputSignPathTable
			spTableSort.Sort(Nothing)
			Dim spInputCursor As ICursor = spTableSort.Rows

			Dim siTableSort As ITableSort = New TableSort()
			siTableSort.Fields = "ID, SEQNR, DESTSEQ, RNPART"
			siTableSort.Ascending("ID") = True
			siTableSort.Ascending("SEQNR") = True
			siTableSort.Ascending("DESTSEQ") = True
			siTableSort.Ascending("RNPART") = True
			siTableSort.QueryFilter = Nothing
			siTableSort.Table = inputSignInformationTable
			siTableSort.Sort(Nothing)
			Dim siInputCursor As ICursor = siTableSort.Rows

			Dim inputSpTableRow As IRow
			Dim inputSiTableRow As IRow
			Dim currentID As Long = -1, loopSpID As Long, loopSiID As Long

			Dim numOutput As Integer = 0
			Dim numInput As Integer = 0
			Dim fetchFeatureDataSucceeded As Boolean
			Dim idVals As ArrayList = New System.Collections.ArrayList(2)
			Dim edgesData As ArrayList = New System.Collections.ArrayList(2)
			Dim reverseEdge As ArrayList = New System.Collections.ArrayList(2)
			Dim currentFeatureData As SignpostUtilities.FeatureData = New SignpostUtilities.FeatureData(-1, Nothing)
			Dim earlierEdgeCurve As ICurve, laterEdgeCurve As ICurve
			Dim earlierEdgeStart As IPoint, earlierEdgeEnd As IPoint
			Dim laterEdgeStart As IPoint, laterEdgeEnd As IPoint

			Dim nextBranchNum As Integer = -1, nextTowardNum As Integer = -1
			Dim infoTypText As String, txtContText As String
			Dim directionalAsText As String = ""
			Dim towardText As String
			Dim conTypVal As Integer

			Dim refLinesFCID As Integer = inputLineFeatures.ObjectClassID
			Dim outputSignGeometry As IGeometry
			Dim newOID As Object

			inputSpTableRow = spInputCursor.NextRow()
			inputSiTableRow = siInputCursor.NextRow()
			While inputSpTableRow IsNot Nothing And inputSiTableRow IsNot Nothing
				currentID = Convert.ToInt64(inputSpTableRow.Value(inSpIdFI))

				' fetch the edge ID values from the SP table for the current sign ID

				idVals.Clear()
				While (True)
					idVals.Add(Convert.ToInt64(inputSpTableRow.Value(inSpTrpElIdFI)))

					inputSpTableRow = spInputCursor.NextRow()
					If (inputSpTableRow Is Nothing) Then
						Exit While	  ' we've reached the end of the SP table
					End If

					loopSpID = Convert.ToInt64(inputSpTableRow.Value(inSpIdFI))
					If (loopSpID <> currentID) Then
						Exit While	  ' we're now on a new ID value					
					End If
				End While

				numInput += 1

				' fetch the FeatureData for each of these edges

				edgesData.Clear()
				fetchFeatureDataSucceeded = True
				For Each currentIDVal As Long In idVals
					Try
						currentFeatureData = CType(lineFeaturesList(currentIDVal), SignpostUtilities.FeatureData)
						edgesData.Add(currentFeatureData)
					Catch
						fetchFeatureDataSucceeded = False
						If (numInput - numOutput < 100) Then
							messages.AddWarning("Line feature not found for sign with ID: " + _
							  Convert.ToString(currentIDVal))
						End If
						Exit For
					End Try
				Next
				If Not fetchFeatureDataSucceeded Then Continue While

				' determine the orientation for each of these edges

				reverseEdge.Clear()
				For i As Integer = 1 To edgesData.Count - 1
					' get the endpoints of the earlier curve
					currentFeatureData = CType(edgesData(i - 1), SignpostUtilities.FeatureData)
					earlierEdgeCurve = CType(currentFeatureData.feature, ICurve)
					earlierEdgeStart = earlierEdgeCurve.FromPoint
					earlierEdgeEnd = earlierEdgeCurve.ToPoint

					' get the endpoints of the later curve
					currentFeatureData = CType(edgesData(i), SignpostUtilities.FeatureData)
					laterEdgeCurve = CType(currentFeatureData.feature, ICurve)
					laterEdgeStart = laterEdgeCurve.FromPoint
					laterEdgeEnd = laterEdgeCurve.ToPoint

					' determine the orientation of the first edge
					' (first edge is reversed if its Start point is coincident with either point of the second edge)
					If (i = 1) Then
						reverseEdge.Add(EqualPoints(earlierEdgeStart, laterEdgeStart) Or EqualPoints(earlierEdgeStart, laterEdgeEnd))
					End If

					' determine the orientation of the i'th edge
					' (i'th edge is reversed if its End point is coincident with either point of the previous edge)
					reverseEdge.Add(EqualPoints(laterEdgeEnd, earlierEdgeStart) Or EqualPoints(laterEdgeEnd, earlierEdgeEnd))
				Next i

				' write out the sign geometry to the featureBuffer

				outputSignGeometry = MakeSignGeometry(edgesData, reverseEdge)
				featureBuffer.Shape = outputSignGeometry

				' fetch the signpost information from the SI table for the current sign ID

				nextBranchNum = 0
				nextTowardNum = 0
				featureBuffer.Value(outExitNameFI) = ""

				While inputSiTableRow IsNot Nothing
					loopSiID = Convert.ToInt64(inputSiTableRow.Value(inSiIdFI))
					If (loopSiID < currentID) Then
						inputSiTableRow = siInputCursor.NextRow()
						Continue While
					ElseIf (loopSiID > currentID) Then
						Exit While	  ' we're now on a new ID value
					End If

					infoTypText = CType(inputSiTableRow.Value(inSiInfoTypFI), String)
					txtContText = CType(inputSiTableRow.Value(inSiTxtContFI), String)
					conTypVal = Convert.ToInt32(inputSiTableRow.Value(inSiConTypFI))

					Select Case infoTypText
						Case "4E"	 ' exit number
							featureBuffer.Value(outExitNameFI) = txtContText

						Case "9D", "4I"	   ' place name or other destination

							' check for schema overflow
							If (nextTowardNum > SignpostUtilities.MaxBranchCount - 1) Then
								inputSiTableRow = siInputCursor.NextRow()
								Continue While
							End If

							' set values
							featureBuffer.Value(outTowardXFI(nextTowardNum)) = txtContText
							featureBuffer.Value(outTowardXLngFI(nextTowardNum)) = "en"

							' get ready for next toward
							nextTowardNum += 1

						Case "6T", "RN"	   ' street name or route number

							If (conTypVal = 2) Then	   ' toward
								' check for schema overflow
								If (nextTowardNum > SignpostUtilities.MaxBranchCount - 1) Then
									inputSiTableRow = siInputCursor.NextRow()
									Continue While
								End If

								' set values
								featureBuffer.Value(outTowardXFI(nextTowardNum)) = txtContText
								featureBuffer.Value(outTowardXLngFI(nextTowardNum)) = "en"

								' get ready for next toward
								nextTowardNum += 1
							Else	' branch
								' check for schema overflow
								If (nextBranchNum > SignpostUtilities.MaxBranchCount - 1) Then
									inputSiTableRow = siInputCursor.NextRow()
									Continue While
								End If

								' set values
								featureBuffer.Value(outBranchXFI(nextBranchNum)) = txtContText
								featureBuffer.Value(outBranchXDirFI(nextBranchNum)) = ""
								featureBuffer.Value(outBranchXLngFI(nextBranchNum)) = "en"

								' get ready for next branch
								nextBranchNum += 1
							End If

						Case "7G"	 ' route directional

							' convert the directional value to a string
							Select Case txtContText
								Case "1"
									directionalAsText = "N"
								Case "2"
									directionalAsText = "E"
								Case "3"
									directionalAsText = "S"
								Case "4"
									directionalAsText = "W"
							End Select

							If (conTypVal = 2) Then	   ' toward
								' check for schema underflow
								If (nextTowardNum = 0) Then
									inputSiTableRow = siInputCursor.NextRow()
									Continue While
								End If

								' append directional text to the previous toward text value
								towardText = CType(featureBuffer.Value(outTowardXFI(nextTowardNum - 1)), String)
								towardText = towardText + " " + directionalAsText
								featureBuffer.Value(outTowardXFI(nextTowardNum - 1)) = towardText
							Else	' branch
								' check for schema underflow
								If (nextBranchNum = 0) Then
									inputSiTableRow = siInputCursor.NextRow()
									Continue While
								End If

								' set value of the Dir field on the previous branch
								featureBuffer.Value(outBranchXDirFI(nextBranchNum - 1)) = directionalAsText
							End If

					End Select

					inputSiTableRow = siInputCursor.NextRow()

				End While  ' each SI table record

				' clean up unused parts of the row and pack toward/branch items

				SignpostUtilities.CleanUpSignpostFeatureValues(featureBuffer, nextBranchNum - 1, nextTowardNum - 1, _
					 outBranchXFI, outBranchXDirFI, outBranchXLngFI, _
					 outTowardXFI, outTowardXLngFI)

				' save sign feature record

				newOID = featureInsertCursor.InsertFeature(featureBuffer)

				' set streets table values

				tableRowBuffer.Value(outTblSignpostIDFI) = newOID
				tableRowBuffer.Value(outTblEdgeFCIDFI) = refLinesFCID
				For i As Integer = 0 To edgesData.Count - 1
					currentFeatureData = CType(edgesData(i), SignpostUtilities.FeatureData)
					tableRowBuffer.Value(outTblSequenceFI) = i + 1
					tableRowBuffer.Value(outTblEdgeFIDFI) = currentFeatureData.OID
					If (CType(reverseEdge(i), Boolean)) Then
						tableRowBuffer.Value(outTblEdgeFrmPosFI) = 1.0
						tableRowBuffer.Value(outTblEdgeToPosFI) = 0.0
					Else
						tableRowBuffer.Value(outTblEdgeFrmPosFI) = 0.0
						tableRowBuffer.Value(outTblEdgeToPosFI) = 1.0
					End If

					' insert detail record

					tableInsertCursor.InsertRow(tableRowBuffer)
				Next i

				numOutput += 1
				If ((numOutput Mod 100) = 0) Then
					' check for user cancel

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

			End While  ' outer while

			' add a summary message

			messages.AddMessage(Convert.ToString(numOutput) + " of " + Convert.ToString(numInput) + " signposts added.")

			Return
		End Sub

		Private Function EqualPoints(ByVal p1 As IPoint, ByVal p2 As IPoint) As Boolean
			Return ((p1.X = p2.X) And (p1.Y = p2.Y))
		End Function

		Private Function MakeSignGeometry(ByVal edgesData As ArrayList, ByVal reverseEdge As ArrayList) As IGeometry
			Dim resultSegments As ISegmentCollection = New Polyline()
			Dim currentFeatureData As SignpostUtilities.FeatureData = New SignpostUtilities.FeatureData(-1, Nothing)
			Dim currentCurve As ICurve, resultCurve As ICurve

			For i As Integer = 0 To edgesData.Count - 1
				' fetch the curve and reverse it as needed

				currentFeatureData = CType(edgesData(i), SignpostUtilities.FeatureData)
				currentCurve = CType(currentFeatureData.feature, ICurve)
				If (CType(reverseEdge(i), Boolean)) Then
					currentCurve.ReverseOrientation()
				End If

				' trim the first and last geometries so that they only cover 25% of the street feature

				If (i = 0) Then
					currentCurve.GetSubcurve(0.75, 1.0, True, resultCurve)
				ElseIf (i = (edgesData.Count - 1)) Then
					currentCurve.GetSubcurve(0.0, 0.25, True, resultCurve)
				Else
					resultCurve = currentCurve
				End If

				' add the resulting geometry to the collection

				resultSegments.AddSegmentCollection(CType(resultCurve, ISegmentCollection))
			Next i

			Return CType(resultSegments, IGeometry)
		End Function
	End Class
End Namespace