ImportMultiNetSignsFunction.cs
// Copyright 2010 ESRI // // All rights reserved under the copyright laws of the United States // and applicable international laws, treaties, and conventions. // // You may freely redistribute and use this sample code, with or // without modification, provided you include the original copyright // notice and use restrictions. // // See the use restrictions. // 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; } } }