private void CreateSignposts(string inputSignsTablePath, string outputFileGdbPath, IGPMessages messages, ITrackCancel trackcancel)
        {
            // Open the input Signs table

            ITable inputSignsTable = m_gpUtils.OpenTableFromString(inputSignsTablePath);

            // Open the Streets feature class

            Type gdbFactoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            var gdbWSF = Activator.CreateInstance(gdbFactoryType) as IWorkspaceFactory;
            var gdbFWS = gdbWSF.OpenFromFile(outputFileGdbPath, 0) as IFeatureWorkspace;
            IFeatureClass inputLineFeatures = gdbFWS.OpenFeatureClass(StreetsFCName);

            // Create the signpost feature class and table

            IFeatureClass outputSignFeatures = SignpostUtilities.CreateSignsFeatureClass(inputLineFeatures, SignpostFCName);
            ITable outputSignDetailTable = SignpostUtilities.CreateSignsDetailTable(inputLineFeatures, SignpostJoinTableName);

            #region Find fields
            //(Validate checked that these exist)
            IFields inputTableFields = inputSignsTable.Fields;
            int inSequenceFI = inputTableFields.FindField("SEQ_NUM");
            int inExitNumFI = inputTableFields.FindField("EXIT_NUM");
            int inFromIDFI = inputTableFields.FindField("SRC_LINKID");
            int inToIDFI = inputTableFields.FindField("DST_LINKID");
            int inLangFI = inputTableFields.FindField("LANG_CODE");
            int inBranchRteIDFI = inputTableFields.FindField("BR_RTEID");
            int inDirectionFI = inputTableFields.FindField("BR_RTEDIR");
            int inToNameFI = inputTableFields.FindField("SIGN_TEXT");
            int inAccessFI = inputTableFields.FindField("SIGN_TXTTP");
            int inToLocaleFI = inputTableFields.FindField("TOW_RTEID");

            // 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, System.Globalization.CultureInfo.InvariantCulture);

                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("LINK_ID");
            int inLinesShapeFI = inputLineFeatures.FindField(inputLineFeatures.ShapeFieldName);

            #endregion

            // Get the language lookup hash

            System.Collections.Hashtable langLookup = CreateLanguageLookup();

            // 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(inputSignsTable, inFromIDFI, inToIDFI, inputLineFeatures, "LINK_ID", 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 cursor for the signs table we are importing

            ITableSort tableSort = new TableSortClass();
            tableSort.Fields = "SRC_LINKID, DST_LINKID, SEQ_NUM";
            tableSort.set_Ascending("SRC_LINKID", true);
            tableSort.set_Ascending("DST_LINKID", true);
            tableSort.set_Ascending("SEQ_NUM", true);
            tableSort.QueryFilter = null;
            tableSort.Table = inputSignsTable;
            tableSort.Sort(null);
            ICursor inputCursor = tableSort.Rows;

            IRow inputTableRow;
            int numOutput = 0;
            int numInput = 0;
            short inSequenceValue;
            long fromIDVal, toIDVal;

            int nextBranchNum = -1, nextTowardNum = -1;

            // these are initialized to prevent uninitialized variable compiler error

            SignpostUtilities.FeatureData fromFeatureData = new SignpostUtilities.FeatureData(-1, null);
            SignpostUtilities.FeatureData toFeatureData = new SignpostUtilities.FeatureData(-1, null);

            object newOID;
            string branchText, towardText, signText, accessText;
            string langText, langValue;

            ICurve fromEdgeCurve, toEdgeCurve;
            IPoint fromEdgeStart, fromEdgeEnd, toEdgeStart, toEdgeEnd;

            int refLinesFCID = inputLineFeatures.ObjectClassID;
            IGeometry outputSignGeometry;

            double lastSrcLinkID = -1.0, currentSrcLinkID = -1.0;
            double lastDstLinkID = -1.0, currentDstLinkID = -1.0;
            double fromEdgeFromPos = 0.0;
            double fromEdgeToPos = 1.0;
            double toEdgeFromPos = 0.0;
            double toEdgeToPos = 1.0;

            while ((inputTableRow = inputCursor.NextRow()) != null)
            {
                currentSrcLinkID = Convert.ToInt32(inputTableRow.get_Value(inFromIDFI));
                currentDstLinkID = Convert.ToInt32(inputTableRow.get_Value(inToIDFI));

                // If we have a new source/destination link ID, we need to
                // insert the signpost feature in progress and write the detail records.
                // (identical code is also after the while loop for the last sign record)

                if (((currentSrcLinkID != lastSrcLinkID) || (currentDstLinkID != lastDstLinkID)) &&
                    ((lastSrcLinkID != -1) && (lastDstLinkID != -1)))
                {
                    // 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(outTblSequenceFI, 1);
                    tableRowBuffer.set_Value(outTblEdgeFCIDFI, refLinesFCID);
                    tableRowBuffer.set_Value(outTblEdgeFIDFI, fromFeatureData.OID);
                    tableRowBuffer.set_Value(outTblEdgeFrmPosFI, fromEdgeFromPos);
                    tableRowBuffer.set_Value(outTblEdgeToPosFI, fromEdgeToPos);

                    // insert first detail record

                    tableInsertCursor.InsertRow(tableRowBuffer);

                    tableRowBuffer.set_Value(outTblSequenceFI, 0);
                    tableRowBuffer.set_Value(outTblEdgeFIDFI, toFeatureData.OID);
                    tableRowBuffer.set_Value(outTblEdgeFrmPosFI, toEdgeFromPos);
                    tableRowBuffer.set_Value(outTblEdgeToPosFI, toEdgeToPos);

                    // insert second detail record

                    tableInsertCursor.InsertRow(tableRowBuffer);

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

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

                lastSrcLinkID = currentSrcLinkID;
                lastDstLinkID = currentDstLinkID;

                inSequenceValue = Convert.ToInt16(inputTableRow.get_Value(inSequenceFI));
                if (inSequenceValue == 1)
                {
                    // We are starting a sequence of records for a new sign.
                    // nextBranchNum and nextTowardNum keep track of which branch and
                    // toward item numbers we have used and are not necessarily the same
                    // as inSequenceValue.

                    nextBranchNum = 0;
                    nextTowardNum = 0;

                    fromIDVal = Convert.ToInt64(inputTableRow.get_Value(inFromIDFI));
                    toIDVal = Convert.ToInt64(inputTableRow.get_Value(inToIDFI));

                    // If the signpost references a line feature that is not in the lines
                    // feature class, add a warning message and keep going.
                    // Only warn for the first 100 not found.

                    numInput++;

                    try
                    {
                        fromFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[fromIDVal];
                        toFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[toIDVal];
                    }
                    catch
                    {
                        if (numInput - numOutput < 100)
                        {
                            messages.AddWarning("Line feature not found for sign with FromID: " +
                                Convert.ToString(fromIDVal, System.Globalization.CultureInfo.InvariantCulture) +
                                ", ToID: " + Convert.ToString(toIDVal, System.Globalization.CultureInfo.InvariantCulture));
                        }
                        continue;
                    }

                    // To set from and to position in the detail table and to construct geometry
                    // for the output signs feature class, we need see where and
                    // if the two edge features connect to figure out their digitized direction.

                    fromEdgeCurve = fromFeatureData.feature as ICurve;
                    toEdgeCurve = toFeatureData.feature as ICurve;

                    fromEdgeStart = fromEdgeCurve.FromPoint;
                    fromEdgeEnd = fromEdgeCurve.ToPoint;
                    toEdgeStart = toEdgeCurve.FromPoint;
                    toEdgeEnd = toEdgeCurve.ToPoint;

                    fromEdgeFromPos = 0.0;
                    fromEdgeToPos = 1.0;
                    toEdgeFromPos = 0.0;
                    toEdgeToPos = 1.0;

                    // flip the from edge?

                    if (TurnGeometryUtilities.EqualPoints(fromEdgeStart, toEdgeStart) || TurnGeometryUtilities.EqualPoints(fromEdgeStart, toEdgeEnd))
                    {
                        fromEdgeFromPos = 1.0;
                        fromEdgeToPos = 0.0;
                    }

                    // flip the to edge?

                    if (TurnGeometryUtilities.EqualPoints(toEdgeEnd, fromEdgeStart) || TurnGeometryUtilities.EqualPoints(toEdgeEnd, fromEdgeEnd))
                    {
                        toEdgeFromPos = 1.0;
                        toEdgeToPos = 0.0;
                    }

                    // set sign feature values

                    // construct shape - the only purpose of the shape is visualization and it can be null

                    outputSignGeometry = MakeSignGeometry(fromEdgeCurve, toEdgeCurve, fromEdgeFromPos == 1.0, toEdgeFromPos == 1.0);

                    featureBuffer.Shape = outputSignGeometry;

                    featureBuffer.set_Value(outExitNameFI, inputTableRow.get_Value(inExitNumFI));
                }

                // Look up the language code

                langText = (inputTableRow.get_Value(inLangFI) as string).Trim();
                langValue = SignpostUtilities.GetLanguageValue(langText, langLookup);

                // Populate Branch items from BR_RTEID and BR_RTEDIR

                branchText = (inputTableRow.get_Value(inBranchRteIDFI) as string).Trim();

                if (branchText.Length > 0)
                {
                    // check for schema overflow
                    if (nextBranchNum > SignpostUtilities.MaxBranchCount - 1)
                        continue;

                    // set values
                    featureBuffer.set_Value(outBranchXFI[nextBranchNum], branchText);
                    featureBuffer.set_Value(outBranchXDirFI[nextBranchNum], inputTableRow.get_Value(inDirectionFI));
                    featureBuffer.set_Value(outBranchXLngFI[nextBranchNum], langValue);

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

                // Populate Branch or Toward items from SIGN_TEXT depending upon the value in the SIGN_TXTTP field:
                //  - if SIGN_TXTTP == "B" (direct), populate a branch
                //  - if SIGN_TXTTP == "T" (direct), populate a toward

                signText = (inputTableRow.get_Value(inToNameFI) as string).Trim();

                if (signText.Length > 0)
                {
                    accessText = (inputTableRow.get_Value(inAccessFI) as string);

                    if (accessText == "B")
                    {
                        // check for schema overflow
                        if (nextBranchNum > SignpostUtilities.MaxBranchCount - 1)
                            continue;

                        // set values
                        featureBuffer.set_Value(outBranchXFI[nextBranchNum], signText);
                        featureBuffer.set_Value(outBranchXDirFI[nextBranchNum], inputTableRow.get_Value(inDirectionFI));
                        featureBuffer.set_Value(outBranchXLngFI[nextBranchNum], langValue);

                        // get ready for next branch
                        nextBranchNum++;
                    }
                    else if (accessText == "T")
                    {
                        // check for schema overflow
                        if (nextTowardNum > SignpostUtilities.MaxBranchCount - 1)
                            continue;

                        // set values
                        featureBuffer.set_Value(outTowardXFI[nextTowardNum], signText);
                        featureBuffer.set_Value(outTowardXLngFI[nextTowardNum], langValue);

                        // get ready for next toward
                        nextTowardNum++;
                    }
                    else
                        continue;    // not expected
                }

                // Populate Toward items from TOW_RTEID

                towardText = (inputTableRow.get_Value(inToLocaleFI) as string).Trim();

                if (towardText.Length > 0)
                {
                    // check for schema overflow
                    if (nextTowardNum > SignpostUtilities.MaxBranchCount - 1)
                        continue;

                    // set values
                    featureBuffer.set_Value(outTowardXFI[nextTowardNum], towardText);
                    featureBuffer.set_Value(outTowardXLngFI[nextTowardNum], langValue);

                    // get ready for next toward
                    nextTowardNum++;
                }

            }  // each input table record

            // Assuming the table wasn't empty to begin with (detected by the Currents no longer being -1.0),
            // add the last signpost feature and detail records (same code as above)

            if (currentSrcLinkID != -1.0 && currentDstLinkID != -1.0)
            {
                // 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(outTblSequenceFI, 1);
                tableRowBuffer.set_Value(outTblEdgeFCIDFI, refLinesFCID);
                tableRowBuffer.set_Value(outTblEdgeFIDFI, fromFeatureData.OID);
                tableRowBuffer.set_Value(outTblEdgeFrmPosFI, fromEdgeFromPos);
                tableRowBuffer.set_Value(outTblEdgeToPosFI, fromEdgeToPos);

                // insert first detail record

                tableInsertCursor.InsertRow(tableRowBuffer);

                tableRowBuffer.set_Value(outTblSequenceFI, 0);
                tableRowBuffer.set_Value(outTblEdgeFIDFI, toFeatureData.OID);
                tableRowBuffer.set_Value(outTblEdgeFrmPosFI, toEdgeFromPos);
                tableRowBuffer.set_Value(outTblEdgeToPosFI, toEdgeToPos);

                // insert second detail record

                tableInsertCursor.InsertRow(tableRowBuffer);

                numOutput++;

                // Flush any outstanding writes to the feature class and table
                featureInsertCursor.Flush();
                tableInsertCursor.Flush();
            }

            // add a summary message

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

            return;
        }
        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;
        }
        private void CreateRoadSplitsTable(string inputRdmsTable, string outputFileGdbPath, IGPMessages messages, ITrackCancel trackcancel)
        {
            // Open the Rdms table and find all the fields we need

            Type factoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            var wsf = Activator.CreateInstance(factoryType) as IWorkspaceFactory;
            var fws = wsf.OpenFromFile(outputFileGdbPath, 0) as IFeatureWorkspace;
            ITable rdmsTable = fws.OpenTable(inputRdmsTable);
            int seqNumberField = rdmsTable.FindField("SEQ_NUMBER");
            int linkIDFieldOnRdms = rdmsTable.FindField("LINK_ID");
            int manLinkIDField = rdmsTable.FindField("MAN_LINKID");
            int condIDFieldOnRdms = rdmsTable.FindField("COND_ID");
            int endOfLkFieldOnRdms = rdmsTable.FindField("END_OF_LK");

            // Open the Streets feature class and get its feature class ID

            IFeatureClass inputLineFeatures = fws.OpenFeatureClass(StreetsFCName);
            int streetsFCID = inputLineFeatures.FeatureClassID;

            // Define the fields for the RoadSplits table

            var ocd = new ObjectClassDescriptionClass() as IObjectClassDescription;
            var fieldsEdit = ocd.RequiredFields as IFieldsEdit;
            IFieldEdit field;

            // Add the anchor edge fields to the table

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

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

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

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

            // Add the branch edge fields to the table

            for (int i = 0; i < 3; i++)
            {
                field = new FieldClass();
                field.Name_2 = "Branch" + i + "FCID";
                field.Type_2 = esriFieldType.esriFieldTypeInteger;
                fieldsEdit.AddField(field);

                field = new FieldClass();
                field.Name_2 = "Branch" + i + "FID";
                field.Type_2 = esriFieldType.esriFieldTypeInteger;
                fieldsEdit.AddField(field);

                field = new FieldClass();
                field.Name_2 = "Branch" + i + "FrmPos";
                field.Type_2 = esriFieldType.esriFieldTypeDouble;
                fieldsEdit.AddField(field);

                field = new FieldClass();
                field.Name_2 = "Branch" + i + "ToPos";
                field.Type_2 = esriFieldType.esriFieldTypeDouble;
                fieldsEdit.AddField(field);
            }

            // Check the fields and create the RoadSplits table

            var fieldChk = new FieldCheckerClass() as IFieldChecker;
            IEnumFieldError enumFieldErr = null;
            IFields validatedFields = null;
            fieldChk.ValidateWorkspace = fws as IWorkspace;
            fieldChk.Validate(fieldsEdit as IFields, out enumFieldErr, out validatedFields);
            var roadSplitsTable = fws.CreateTable(RoadSplitsTableName, validatedFields, ocd.InstanceCLSID, ocd.ClassExtensionCLSID, "") as ITable;

            // Find all the fields
            int EdgeFCIDFI = roadSplitsTable.FindField("EdgeFCID");
            int EdgeFIDFI = roadSplitsTable.FindField("EdgeFID");
            int EdgeFrmPosFI = roadSplitsTable.FindField("EdgeFrmPos");
            int EdgeToPosFI = roadSplitsTable.FindField("EdgeToPos");
            int Branch0FCIDFI = roadSplitsTable.FindField("Branch0FCID");
            int Branch0FIDFI = roadSplitsTable.FindField("Branch0FID");
            int Branch0FrmPosFI = roadSplitsTable.FindField("Branch0FrmPos");
            int Branch0ToPosFI = roadSplitsTable.FindField("Branch0ToPos");
            int Branch1FCIDFI = roadSplitsTable.FindField("Branch1FCID");
            int Branch1FIDFI = roadSplitsTable.FindField("Branch1FID");
            int Branch1FrmPosFI = roadSplitsTable.FindField("Branch1FrmPos");
            int Branch1ToPosFI = roadSplitsTable.FindField("Branch1ToPos");
            int Branch2FCIDFI = roadSplitsTable.FindField("Branch2FCID");
            int Branch2FIDFI = roadSplitsTable.FindField("Branch2FID");
            int Branch2FrmPosFI = roadSplitsTable.FindField("Branch2FrmPos");
            int Branch2ToPosFI = roadSplitsTable.FindField("Branch2ToPos");

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

            System.Collections.Hashtable lineFeaturesList = SignpostUtilities.FillFeatureCache(rdmsTable, linkIDFieldOnRdms, manLinkIDField,
                                                                                               inputLineFeatures, "LINK_ID", trackcancel);

            // Create insert cursor and row buffer for output

            ICursor tableInsertCursor = roadSplitsTable.Insert(true);
            IRowBuffer tableBuffer = roadSplitsTable.CreateRowBuffer();
            IRow row = tableBuffer as IRow;

            // Create input cursor for the Rdms table we are importing

            ITableSort tableSort = new TableSortClass();
            tableSort.Fields = "LINK_ID, COND_ID, SEQ_NUMBER";
            tableSort.set_Ascending("LINK_ID", true);
            tableSort.set_Ascending("COND_ID", true);
            tableSort.set_Ascending("SEQ_NUMBER", true);
            tableSort.QueryFilter = null;
            tableSort.Table = rdmsTable;
            tableSort.Sort(null);
            ICursor inputCursor = tableSort.Rows;

            IRow inputTableRow = inputCursor.NextRow();
            if (inputTableRow == null)
                return;     // if Rdms table is empty, there's nothing to do

            // these are initialized to prevent uninitialized variable compiler error
            SignpostUtilities.FeatureData linkFeatureData = new SignpostUtilities.FeatureData(-1, null);
            SignpostUtilities.FeatureData manLinkFeatureData = new SignpostUtilities.FeatureData(-1, null);

            ICurve fromEdgeCurve, toEdgeCurve;
            IPoint fromEdgeStart, fromEdgeEnd, toEdgeStart, toEdgeEnd;
            double fromEdgeFromPos = 0.0;
            double fromEdgeToPos = 1.0;
            double toEdgeFromPos = 0.0;
            double toEdgeToPos = 1.0;

            long currentLinkID = Convert.ToInt64(inputTableRow.get_Value(linkIDFieldOnRdms));
            long manLinkID = Convert.ToInt64(inputTableRow.get_Value(manLinkIDField));
            try
            {
                linkFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[currentLinkID];
                manLinkFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[manLinkID];

                // To set from and to position in the output table, we need see where and
                // if the two edge features connect to figure out their digitized direction.

                fromEdgeCurve = linkFeatureData.feature as ICurve;
                toEdgeCurve = manLinkFeatureData.feature as ICurve;

                fromEdgeStart = fromEdgeCurve.FromPoint;
                fromEdgeEnd = fromEdgeCurve.ToPoint;
                toEdgeStart = toEdgeCurve.FromPoint;
                toEdgeEnd = toEdgeCurve.ToPoint;

                // flip the from edge?

                if (TurnGeometryUtilities.EqualPoints(fromEdgeStart, toEdgeStart) || TurnGeometryUtilities.EqualPoints(fromEdgeStart, toEdgeEnd))
                {
                    fromEdgeFromPos = 1.0;
                    fromEdgeToPos = 0.0;
                }

                // flip the to edge?

                if (TurnGeometryUtilities.EqualPoints(toEdgeEnd, fromEdgeStart) || TurnGeometryUtilities.EqualPoints(toEdgeEnd, fromEdgeEnd))
                {
                    toEdgeFromPos = 1.0;
                    toEdgeToPos = 0.0;
                }

                // set the field values in the buffer

                tableBuffer.set_Value(EdgeFCIDFI, streetsFCID);
                tableBuffer.set_Value(EdgeFIDFI, linkFeatureData.OID);
                tableBuffer.set_Value(EdgeFrmPosFI, fromEdgeFromPos);
                tableBuffer.set_Value(EdgeToPosFI, fromEdgeToPos);
                tableBuffer.set_Value(Branch0FCIDFI, streetsFCID);
                tableBuffer.set_Value(Branch0FIDFI, manLinkFeatureData.OID);
                tableBuffer.set_Value(Branch0FrmPosFI, toEdgeFromPos);
                tableBuffer.set_Value(Branch0ToPosFI, toEdgeToPos);
                tableBuffer.set_Value(Branch1FCIDFI, null);
                tableBuffer.set_Value(Branch1FIDFI, null);
                tableBuffer.set_Value(Branch1FrmPosFI, null);
                tableBuffer.set_Value(Branch1ToPosFI, null);
                tableBuffer.set_Value(Branch2FCIDFI, null);
                tableBuffer.set_Value(Branch2FIDFI, null);
                tableBuffer.set_Value(Branch2FrmPosFI, null);
                tableBuffer.set_Value(Branch2ToPosFI, null);
            }
            catch
            {
                messages.AddWarning("Line feature not found for explication with from ID: " +
                    Convert.ToString(currentLinkID, System.Globalization.CultureInfo.InvariantCulture) +
                    ", To ID: " + Convert.ToString(manLinkID, System.Globalization.CultureInfo.InvariantCulture));
            }

            long previousLinkID = currentLinkID;
            int nextBranch = 1;

            while ((inputTableRow = inputCursor.NextRow()) != null)
            {
                currentLinkID = Convert.ToInt64(inputTableRow.get_Value(linkIDFieldOnRdms));
                manLinkID = Convert.ToInt64(inputTableRow.get_Value(manLinkIDField));
                try
                {
                    linkFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[currentLinkID];
                    manLinkFeatureData = (SignpostUtilities.FeatureData)lineFeaturesList[manLinkID];
                }
                catch
                {
                    messages.AddWarning("Line feature not found for explication with from ID: " +
                        Convert.ToString(currentLinkID, System.Globalization.CultureInfo.InvariantCulture) +
                        ", To ID: " + Convert.ToString(manLinkID, System.Globalization.CultureInfo.InvariantCulture));
                    continue;
                }

                // To set from and to position in the output table, we need see where and
                // if the two edge features connect to figure out their digitized direction.

                fromEdgeCurve = linkFeatureData.feature as ICurve;
                toEdgeCurve = manLinkFeatureData.feature as ICurve;

                fromEdgeStart = fromEdgeCurve.FromPoint;
                fromEdgeEnd = fromEdgeCurve.ToPoint;
                toEdgeStart = toEdgeCurve.FromPoint;
                toEdgeEnd = toEdgeCurve.ToPoint;

                fromEdgeFromPos = 0.0;
                fromEdgeToPos = 1.0;
                toEdgeFromPos = 0.0;
                toEdgeToPos = 1.0;

                // flip the from edge?

                if (TurnGeometryUtilities.EqualPoints(fromEdgeStart, toEdgeStart) || TurnGeometryUtilities.EqualPoints(fromEdgeStart, toEdgeEnd))
                {
                    fromEdgeFromPos = 1.0;
                    fromEdgeToPos = 0.0;
                }

                // flip the to edge?

                if (TurnGeometryUtilities.EqualPoints(toEdgeEnd, fromEdgeStart) || TurnGeometryUtilities.EqualPoints(toEdgeEnd, fromEdgeEnd))
                {
                    toEdgeFromPos = 1.0;
                    toEdgeToPos = 0.0;
                }

                // set the field values in the buffer

                if (previousLinkID == currentLinkID)
                {
                    switch (nextBranch)
                    {
                        case 1:
                            tableBuffer.set_Value(Branch1FCIDFI, streetsFCID);
                            tableBuffer.set_Value(Branch1FIDFI, manLinkFeatureData.OID);
                            tableBuffer.set_Value(Branch1FrmPosFI, toEdgeFromPos);
                            tableBuffer.set_Value(Branch1ToPosFI, toEdgeToPos);
                            nextBranch = 2;
                            break;
                        case 2:
                            tableBuffer.set_Value(Branch2FCIDFI, streetsFCID);
                            tableBuffer.set_Value(Branch2FIDFI, manLinkFeatureData.OID);
                            tableBuffer.set_Value(Branch2FrmPosFI, toEdgeFromPos);
                            tableBuffer.set_Value(Branch2ToPosFI, toEdgeToPos);
                            nextBranch = 3;
                            break;
                        case 3:
                            messages.AddWarning("There are more than three road splits for From ID: " +
                                Convert.ToString(currentLinkID, System.Globalization.CultureInfo.InvariantCulture));
                            nextBranch = 4;
                            break;
                        case 4:
                            // do nothing here, as there's no need to repeat the warning message.
                            break;
                    }
                }
                else
                {
                    // write out the previous buffered row...
                    tableInsertCursor.InsertRow(tableBuffer);

                    // ...and then set field values in the fresh buffer
                    tableBuffer.set_Value(EdgeFCIDFI, streetsFCID);
                    tableBuffer.set_Value(EdgeFIDFI, linkFeatureData.OID);
                    tableBuffer.set_Value(EdgeFrmPosFI, fromEdgeFromPos);
                    tableBuffer.set_Value(EdgeToPosFI, fromEdgeToPos);
                    tableBuffer.set_Value(Branch0FCIDFI, streetsFCID);
                    tableBuffer.set_Value(Branch0FIDFI, manLinkFeatureData.OID);
                    tableBuffer.set_Value(Branch0FrmPosFI, toEdgeFromPos);
                    tableBuffer.set_Value(Branch0ToPosFI, toEdgeToPos);
                    tableBuffer.set_Value(Branch1FCIDFI, null);
                    tableBuffer.set_Value(Branch1FIDFI, null);
                    tableBuffer.set_Value(Branch1FrmPosFI, null);
                    tableBuffer.set_Value(Branch1ToPosFI, null);
                    tableBuffer.set_Value(Branch2FCIDFI, null);
                    tableBuffer.set_Value(Branch2FIDFI, null);
                    tableBuffer.set_Value(Branch2FrmPosFI, null);
                    tableBuffer.set_Value(Branch2ToPosFI, null);
                    nextBranch = 1;
                }
                previousLinkID = currentLinkID;
            }

            // Write out the final row and flush
            tableInsertCursor.InsertRow(tableBuffer);
            tableInsertCursor.Flush();
        }
        private void CreateSignposts(string inputSITablePath, string inputSPTablePath, string outputFileGdbPath, 
                                     IGPMessages messages, ITrackCancel trackcancel)
        {
            // Open the input SI and SP tables

            ITable inputSignInformationTable = m_gpUtils.OpenTableFromString(inputSITablePath);
            ITable inputSignPathTable = m_gpUtils.OpenTableFromString(inputSPTablePath);

            // Open the Streets feature class

            Type gdbFactoryType = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
            var gdbWSF = Activator.CreateInstance(gdbFactoryType) as IWorkspaceFactory;
            var gdbFWS = gdbWSF.OpenFromFile(outputFileGdbPath, 0) as IFeatureWorkspace;
            IFeatureClass inputLineFeatures = gdbFWS.OpenFeatureClass(StreetsFCName);

            // Create the signpost feature class and table

            IFeatureClass outputSignFeatures = SignpostUtilities.CreateSignsFeatureClass(inputLineFeatures, SignpostFCName);
            ITable outputSignDetailTable = SignpostUtilities.CreateSignsDetailTable(inputLineFeatures, SignpostJoinTableName);

            #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 inSiTxtContLCFI = inputSITableFields.FindField("TXTCONTLC");
            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, System.Globalization.CultureInfo.InvariantCulture);

                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("ID");
            int inLinesShapeFI = inputLineFeatures.FindField(inputLineFeatures.ShapeFieldName);

            #endregion

            // Get the language lookup hash

            System.Collections.Hashtable langLookup = CreateLanguageLookup();

            // 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, "ID", 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 txtContTextLC, langVal;
            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 signpost with ID: " +
                                Convert.ToString(currentIDVal, System.Globalization.CultureInfo.InvariantCulture));
                        }
                        break;
                    }
                }
                if (!fetchFeatureDataSucceeded)
                    continue;

                if (edgesData.Count == 1)
                {
                    messages.AddWarning("Signpost with ID " + Convert.ToString(currentID, System.Globalization.CultureInfo.InvariantCulture)
                        + " only has one transportation element.");
                    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(TurnGeometryUtilities.EqualPoints(earlierEdgeStart, laterEdgeStart) || TurnGeometryUtilities.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(TurnGeometryUtilities.EqualPoints(laterEdgeEnd, earlierEdgeStart) || TurnGeometryUtilities.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;
                    txtContTextLC = inputSiTableRow.get_Value(inSiTxtContLCFI) as string;
                    langVal = SignpostUtilities.GetLanguageValue(txtContTextLC, langLookup);
                    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], langVal);

                            // 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], langVal);

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

                // insert 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 != null && !trackcancel.Continue())
                        throw (new COMException("Function cancelled."));
                }

            }  // outer while

            // Flush any outstanding writes to the feature class and table
            featureInsertCursor.Flush();
            tableInsertCursor.Flush();

            // add a summary message

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

            return;
        }