Import signposts
SignpostUtilities.vb
' 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.
' 

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

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

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

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

    Public Sub New()
    End Sub

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

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

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

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

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

            ' add the other fields to the feature class

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

      Dim currentNumber As String

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

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

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

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

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

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

      ' make the feature class

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

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

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

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

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

      ' add the SignpostID field to the table

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

      ' add the other fields to the table

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

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

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

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

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

      ' make the table

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

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

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

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

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

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

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

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

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

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

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

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

          row = inCursor.NextRow()
        End While
      End If

      ' make the query filter for fetching features

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

      ' Now fetch batches of features

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

      Dim linesIDFieldIndex As Integer = inputLineFeatures.FindField(linesIDFieldName)

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

      If numFeaturesPerQuery > numToFetch Then
        numFeaturesPerQuery = numToFetch
      End If

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

        Dim whereClause As String = linesIDFieldName + " IN("

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

        queryFilter.WhereClause = whereClause

        ' select the features

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

        ' get the features

        Dim feature As IFeature

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

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

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

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

          feature = inputFeatureCursor.NextFeature()
        End While

        ' finished? set up for next batch

        totalDone += numFeaturesPerQuery

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

      Return outputFeatures
    End Function

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

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

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

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