ImportDynamapSignsFunction.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 ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.Geodatabase; using ESRI.ArcGIS.Geoprocessing; using ESRI.ArcGIS.Geometry; namespace GPImportSignpostFunctions { /// <summary> /// /// </summary> /// [Guid("DD0FD598-2622-4240-96B6-6C9FFA43B177")] [ClassInterface(ClassInterfaceType.None)] [ProgId("GPImportSignpostFunctions.ImportDynamapSignsFunction")] public class ImportDynamapSignsFunction : IGPFunction { #region Constants // parameter index constants private const int InputTable = 0; private const int ReferenceLineFeatures = 1; private const int OutFeatureClassName = 2; private const int OutStreetsTableName = 3; // field names and types private static readonly string[] FieldNames = new string[] {"EXIT_ID", "SEQUENCE", "FROM_ID", "FROM_NAME", "EXIT_NUM", "TO_ID", "TO_NAME", "SHIELD", "HWY_NUM", "DIRECTION", "TO_LOCALE", "ACCESS", "EXIT_ONLY", "LONGITUDE", "LATITUDE"}; private static readonly esriFieldType[] FieldTypes = new esriFieldType[] {esriFieldType.esriFieldTypeDouble, esriFieldType.esriFieldTypeSmallInteger, esriFieldType.esriFieldTypeDouble, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeDouble, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeString, esriFieldType.esriFieldTypeDouble, esriFieldType.esriFieldTypeDouble}; private static readonly string LinesIDFieldName = "Dynamap_ID"; private static readonly esriFieldType LinesIDFieldType = esriFieldType.esriFieldTypeDouble; #endregion IArray m_parameters; public ImportDynamapSignsFunction() { } #region IGPFunction Members public IArray ParameterInfo { // create and return the parameters for this function: // 1 - Input Highway Sign Features // 2 - Input Street Features // 3 - Output Signpost Feature Class Name // 4 - Output Signpost Streets Table Name get { IArray paramArray = new ArrayClass(); // 1 - input_highway_sign_features IGPParameterEdit paramEdit = new GPParameterClass(); paramEdit.DataType = new DETableTypeClass() as IGPDataType; paramEdit.Value = new DETableClass() as IGPValue; paramEdit.Direction = esriGPParameterDirection.esriGPParameterDirectionInput; paramEdit.DisplayName = "Input Highway Sign Features"; paramEdit.Enabled = true; paramEdit.Name = "input_highway_sign_features"; paramEdit.ParameterType = esriGPParameterType.esriGPParameterTypeRequired; paramArray.Add(paramEdit as object); // 2 - 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); // 3 - 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); // 4 - 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 table has the expected fields IGPParameter gpParam = paramvalues.get_Element(InputTable) 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, validateMessages.GetMessage(InputTable)); } // 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 inputTable; IFeatureClass inputLineFeatures; IGPParameter gpParam = paramvalues.get_Element(InputTable) as IGPParameter; IGPValue inputTableValue = gpUtils.UnpackGPValue(gpParam); IDataset dataset = gpUtils.OpenDataset(inputTableValue); if (dataset != null) inputTable = dataset as ITable; else { messages.AddError(1, "Could not open input 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(inputTable, inputLineFeatures, outputSignsFeatureClass, outputSignDetailTable, messages, trackcancel); #endregion } catch (COMException e) { messages.AddError(1, e.Message); } } public string DisplayName { get { return "Import Dynamap Signs"; } } public string MetadataFile { get { return "ImportDynamapSignsHelp.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 "ImportDynamapSigns"; } } 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, 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 inputTable, IGPMessage gpMessage) { IFields fields = inputTable.Fields; int fieldIndex = fields.FindField(LinesIDFieldName); if (fieldIndex == -1) { gpMessage.Type = esriGPMessageType.esriGPMessageTypeError; gpMessage.Description = "Field named " + LinesIDFieldName + " not found."; return false; } if (fields.get_Field(fieldIndex).Type != LinesIDFieldType) { gpMessage.Type = esriGPMessageType.esriGPMessageTypeError; gpMessage.Description = "Field named " + LinesIDFieldName + " is not the expected type."; return false; } return true; } private void PopulateData(ITable inputSignsTable, IFeatureClass inputLineFeatures, IFeatureClass outputSignFeatures, ITable outputSignDetailTable, IGPMessages messages, ITrackCancel trackcancel) { #region Find fields //(Validate checked that these exist) IFields inputTableFields = inputSignsTable.Fields; int inExitIDFI = inputTableFields.FindField("EXIT_ID"); int inSequenceFI = inputTableFields.FindField("SEQUENCE"); int inFromIDFI = inputTableFields.FindField("FROM_ID"); int inExitNumFI = inputTableFields.FindField("EXIT_NUM"); int inToIDFI = inputTableFields.FindField("TO_ID"); int inToNameFI = inputTableFields.FindField("TO_NAME"); int inDirectionFI = inputTableFields.FindField("DIRECTION"); int inToLocaleFI = inputTableFields.FindField("TO_LOCALE"); int inAccessFI = inputTableFields.FindField("ACCESS"); /// 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(inputSignsTable, inFromIDFI, inToIDFI, 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 cursor for the signs table we are importing ITableSort tableSort = new TableSortClass(); tableSort.Fields = "EXIT_ID, SEQUENCE"; tableSort.set_Ascending("EXIT_ID", true); tableSort.set_Ascending("SEQUENCE", true); tableSort.QueryFilter = null; tableSort.Table = inputSignsTable; tableSort.Sort(null); ICursor inputCursor = tableSort.Rows; IRow inputTableRow; int numInput = 0; int numOutput = 0; int 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, accessVal; string outputText, outputDirText; ICurve fromEdgeCurve, toEdgeCurve; IPoint fromEdgeStart, fromEdgeEnd, toEdgeStart, toEdgeEnd; int refLinesFCID = inputLineFeatures.ObjectClassID; IGeometry outputSignGeometry; double lastSignID = -1.0, currentSignID = -1.0; double fromEdgeFromPos = 0.0; double fromEdgeToPos = 1.0; double toEdgeFromPos = 0.0; double toEdgeToPos = 1.0; while ((inputTableRow = inputCursor.NextRow()) != null) { currentSignID = Convert.ToInt32(inputTableRow.get_Value(inExitIDFI)); // If we have a new sign 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 (currentSignID != lastSignID && lastSignID != -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, 2); 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.Continue()) throw (new COMException("Function cancelled.")); } } lastSignID = currentSignID; inSequenceValue = Convert.ToInt32(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) + ", ToID: " + Convert.ToString(toIDVal)); } 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 (EqualPoints(fromEdgeStart, toEdgeStart) || EqualPoints(fromEdgeStart, toEdgeEnd)) { fromEdgeFromPos = 1.0; fromEdgeToPos = 0.0; } // flip the to edge? if (EqualPoints(toEdgeEnd, fromEdgeStart) || 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)); } // Populate branch or toward item depending upon the value in the ACCESS field: // if ACCESS == "D" (direct), populate branch(s) // if ACCESS == "I" (direct), populate a toward(s) accessVal = inputTableRow.get_Value(inAccessFI); if ((accessVal as string) == "D") { // check for schema overflow if (nextBranchNum > SignpostUtilities.MaxBranchCount - 1) continue; outputText = (inputTableRow.get_Value(inToNameFI) as String).Trim(); if (outputText.Length > 0) { // set values featureBuffer.set_Value(outBranchXFI[nextBranchNum], outputText); featureBuffer.set_Value(outBranchXDirFI[nextBranchNum], inputTableRow.get_Value(inDirectionFI)); featureBuffer.set_Value(outBranchXLngFI[nextBranchNum], "en"); // get ready for next branch nextBranchNum++; } // there are rare cases when we'll have Access == D (TO_NAME) AND data for TO_LOCALE outputText = (inputTableRow.get_Value(inToLocaleFI) as String).Trim(); if (outputText.Length > 0) { // set values featureBuffer.set_Value(outBranchXFI[nextBranchNum], outputText); featureBuffer.set_Value(outBranchXDirFI[nextBranchNum], null); featureBuffer.set_Value(outBranchXLngFI[nextBranchNum], "en"); // get ready for next branch nextBranchNum++; } } else if ((accessVal as string) == "I") { // check for schema overflow if (nextTowardNum > SignpostUtilities.MaxBranchCount - 1) continue; outputText = (inputTableRow.get_Value(inToNameFI) as String).Trim(); if (outputText.Length > 0) { outputDirText = (inputTableRow.get_Value(inDirectionFI) as String).Trim(); if (outputDirText.Length > 0) { outputText += " "; outputText += outputDirText; } // set values featureBuffer.set_Value(outTowardXFI[nextTowardNum], outputText); featureBuffer.set_Value(outTowardXLngFI[nextTowardNum], "en"); // get ready for next toward nextTowardNum++; } // there are rare cases when we'll have Access == I (TO_LOCALE) AND data for TO_NAME outputText = (inputTableRow.get_Value(inToLocaleFI) as String).Trim(); if (outputText.Length > 0) { // set values featureBuffer.set_Value(outTowardXFI[nextTowardNum], outputText); featureBuffer.set_Value(outTowardXLngFI[nextTowardNum], "en"); // get ready for next toward nextTowardNum++; } } else continue; // not expected } // each input table record // add the last signpost feature and detail records (same code as above) // clean up unused parts of the row 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, 2); tableRowBuffer.set_Value(outTblEdgeFIDFI, toFeatureData.OID); tableRowBuffer.set_Value(outTblEdgeFrmPosFI, toEdgeFromPos); tableRowBuffer.set_Value(outTblEdgeToPosFI, toEdgeToPos); // insert second detail record tableInsertCursor.InsertRow(tableRowBuffer); numOutput++; // 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(ICurve fromEdgeCurve, ICurve toEdgeCurve, bool reverseFromEdge, bool reverseToEdge) { ISegmentCollection resultSegments = new PolylineClass(); ICurve fromResultCurve, toResultCurve; // add the part from the first line if (reverseFromEdge) { fromEdgeCurve.GetSubcurve(0.0, 0.25, true, out fromResultCurve); fromResultCurve.ReverseOrientation(); } else { fromEdgeCurve.GetSubcurve(0.75, 1.0, true, out fromResultCurve); } resultSegments.AddSegmentCollection(fromResultCurve as ISegmentCollection); // add the part from the second line if (reverseToEdge) { toEdgeCurve.GetSubcurve(0.75, 1.0, true, out toResultCurve); toResultCurve.ReverseOrientation(); } else { toEdgeCurve.GetSubcurve(0.0, 0.25, true, out toResultCurve); } resultSegments.AddSegmentCollection(toResultCurve as ISegmentCollection); return resultSegments as IGeometry; } } }