ArcGIS Network Analyst extension barrier location editor
EditorForm.vb
' Copyright 2012 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 Microsoft.VisualBasic
Imports System
Imports System.Windows.Forms
Imports ESRI.ArcGIS.ArcMapUI
Imports ESRI.ArcGIS.Display
Imports ESRI.ArcGIS.Framework
Imports ESRI.ArcGIS.Geodatabase
Imports ESRI.ArcGIS.Geometry
Imports ESRI.ArcGIS.NetworkAnalyst

Namespace NABarrierLocationEditor
  Partial Public Class EditorForm
    Inherits Form
#Region "Member Variables"

    Private Shared ReadOnly EDGE_ALONG As String = "Along Digitized"
    Private Shared ReadOnly EDGE_AGAINST As String = "Against Digitized"

    Private m_app As IApplication
    Private m_context As INAContext
    Private m_barrier As IFeature

#End Region

#Region "Initialization"

    Public Sub New(ByVal app As IApplication, ByVal context As INAContext, ByVal barrier As IFeature)
      m_barrier = barrier
      m_app = app
      m_context = context

      InitializeComponent()
      LoadDatagrids()
    End Sub

    ''' <summary>
    ''' Load both the Edge and Junction dataGrids with the information in the barrier feature
    ''' <param name="barrier">The barrier being loaded into dataGrids</param>
    ''' </summary>
    Private Sub LoadDatagrids()
      ' Populate the cell with the direction drop down
      CType(dataGridViewEdges.Columns(1), DataGridViewComboBoxColumn).Items.AddRange(EDGE_ALONG, EDGE_AGAINST)


      ' get the location ranges out of the barrier feature
      Dim naLocRangesObject As INALocationRangesObject = TryCast(m_barrier, INALocationRangesObject)
      Dim naLocRanges As INALocationRanges = naLocRangesObject.NALocationRanges
      If (naLocRanges Is Nothing) Then
        Throw New Exception("Selected barrier has a null NALocationRanges value")
      End If

      ' add all of the junctions included in the barrier to the Junctions dataGrid
      Dim junctionCount As Integer = naLocRanges.JunctionCount
      Dim junctionEID As Integer
      For i As Integer = 0 To junctionCount - 1
        naLocRanges.QueryJunction(i, junctionEID)
        Dim rowIndex As Integer = dataGridViewJunctions.Rows.Add()
        dataGridViewJunctions.Rows(rowIndex).SetValues(junctionEID)
      Next i

      ' add all of the edges included in the barrier to the Edges dataGrid
      Dim edgeRangeCount As Integer = naLocRanges.EdgeRangeCount
      Dim edgeEID As Integer
      Dim fromPosition, toPosition As Double
      Dim edgeDirection As esriNetworkEdgeDirection
      For i As Integer = 0 To edgeRangeCount - 1
        naLocRanges.QueryEdgeRange(i, edgeEID, edgeDirection, fromPosition, toPosition)

        Dim directionValue As String = ""
        If edgeDirection = esriNetworkEdgeDirection.esriNEDAlongDigitized Then
          directionValue = EDGE_ALONG
        ElseIf edgeDirection = esriNetworkEdgeDirection.esriNEDAgainstDigitized Then
          directionValue = EDGE_AGAINST
        End If

        dataGridViewEdges.Rows.Add(edgeEID, directionValue, fromPosition, toPosition)
      Next i
    End Sub

#End Region

#Region "Button Clicks"

    ''' <summary>
    ''' Occurs when the user clicks the Cancel button.
    ''' <param name="sender">The control raising this event</param>
    ''' <param name="e">Arguments associated with the event</param>
    ''' </summary>
    Private Sub btnCancel_Click(ByVal sender As Object, ByVal e As EventArgs)
      Me.Close()
    End Sub

    ''' <summary>
    ''' Occurs when the user clicks the Save button.
    '''   The barrier information is collected out of the junction and edge barrier
    '''   dataGrids, then stored back into the original barrier feature as a replacement
    '''   to the existing barrier information.  The original geometry of the barrier remains 
    '''   unaltered.
    ''' <param name="sender">The control raising this event</param>
    ''' <param name="e">Arguments associated with the event</param>
    ''' </summary>
    Private Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSave.Click
      If (Not ValidateDataGrid(dataGridViewEdges)) Then
        Return
      End If
      If (Not ValidateDataGrid(dataGridViewJunctions)) Then
        Return
      End If

      ' The existing NALocationRanges for the barrier will be replaced with a new one
      Dim naLocRanges As INALocationRanges = New NALocationRangesClass()

      ' First gather the edge ranges
      For Each row As DataGridViewRow In dataGridViewEdges.Rows
        ' ignore the extra row in the dataGrid
        If row.IsNewRow Then
          Continue For
        End If

        ' gather the EID value for the new range
        Dim eid As Integer = Int32.Parse(row.Cells(0).Value.ToString())

        ' gather the edge direction value for the new range
        Dim directionValue As String = row.Cells(1).Value.ToString()
        Dim direction As esriNetworkEdgeDirection = esriNetworkEdgeDirection.esriNEDNone
        If directionValue = EDGE_ALONG Then
          direction = esriNetworkEdgeDirection.esriNEDAlongDigitized
        ElseIf directionValue = EDGE_AGAINST Then
          direction = esriNetworkEdgeDirection.esriNEDAgainstDigitized
        End If

        ' gather the from and to position values for the new range
        Dim fromPos As Double = Double.Parse(row.Cells(2).Value.ToString())
        Dim toPos As Double = Double.Parse(row.Cells(3).Value.ToString())

        ' load the values for this range into the NALocationRanges object
        naLocRanges.AddEdgeRange(eid, direction, fromPos, toPos)
      Next row

      ' Now gather the junctions to be included in the barrier
      For Each row As DataGridViewRow In dataGridViewJunctions.Rows
        ' ignore the extra row in the dataGrid
        If row.IsNewRow Then
          Continue For
        End If

        ' gather the EID value for the junction to include
        Dim eid As Integer = Int32.Parse(row.Cells(0).Value.ToString())

        ' load this junction into the NALocationRanges object
        naLocRanges.AddJunction(eid)
      Next row

      ' Cast the barrier feature to INALocationRanges Object, then populate
      '   its NALocationRanges value with the new barrier that was created above.
      '   Then, save the new barrier with a call to Store()
      Dim naLocationRangesObject As INALocationRangesObject = TryCast(m_barrier, INALocationRangesObject)
      naLocationRangesObject.NALocationRanges = naLocRanges
      m_barrier.Store()

      Me.Close()
    End Sub

    ''' <summary>
    ''' Occurs when the user clicks the Zoom To Barrier Geometry button.
    '''   The map will zoom to the extent of the Shape of the barrier.
    ''' <param name="sender">The control raising this event</param>
    ''' <param name="e">Arguments associated with the event</param>
    ''' </summary>
    Private Sub btnZoomToBarrier_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnZoomToBarrier.Click
      Dim mxDoc As IMxDocument = TryCast(m_app.Document, IMxDocument)
      mxDoc.ActiveView.Extent = m_barrier.Extent
      mxDoc.ActiveView.Refresh()
    End Sub

#End Region

#Region "Validation"

    ''' <summary>
    ''' ValidateDataGrid goes row by row and checks that the values in the grid
    '''   are valid
    ''' <param name="dgv">The dataGrid to validate</param>
    ''' </summary>
    Private Function ValidateDataGrid(ByVal dgv As DataGridView) As Boolean
      ' we do all of our validation when the save button is clicked
      For Each row As DataGridViewRow In dgv.Rows
        ValidateRow(row)
        If row.ErrorText <> "" Then
          dgv.FirstDisplayedScrollingRowIndex = row.Index
          System.Windows.Forms.MessageBox.Show("You cannot save until all row errors are cleared.", "Barrier Location Editor Warning")
          Return False
        End If
      Next row

      Return True
    End Function

    ''' <summary>
    ''' ValidateRow checks the cell value in the passed-in row
    ''' <param name="row">The row to validate</param>
    ''' <param name="columns">The fields to be validated</param>
    ''' <param name="datagridviewName">The name of the dataGrid whose row is being validated</param>
    ''' </summary>
    Private Sub ValidateRow(ByVal row As DataGridViewRow)
      ' the extra row to add values does not need to be validated
      If row.IsNewRow Then
        Return
      End If

      row.ErrorText = ""

      ' validate each column
      For Each column As DataGridViewColumn In row.DataGridView.Columns
        ' none of the column values can be empty
        If row.Cells(column.Name).Value Is Nothing OrElse row.Cells(column.Name).Value.ToString() = "" Then
          row.ErrorText = "There cannot be any empty cells"
          Return
        End If

        Dim value As String = row.Cells(column.Name).Value.ToString()

        Select Case column.Name
          Case "JunctionEID"
            If (Not ValidateEID(value, esriNetworkElementType.esriNETJunction)) Then
              row.ErrorText &= "  Junction EID must correspond to a valid network junction"
            End If
          Case "EdgeEID"
            If (Not ValidateEID(value, esriNetworkElementType.esriNETEdge)) Then
              row.ErrorText &= "  Edge EID must correspond to a valid network edge"
            End If
          Case "Direction"
            If value <> "Along Digitized" AndAlso value <> "Against Digitized" Then
              row.ErrorText &= "  Direction must be Along or Against Digitized"
            End If
          Case "fromPos"
            Dim fromPos As Double = -1
            If (Not Double.TryParse(value, fromPos)) OrElse fromPos < 0 OrElse fromPos > 1 Then
              row.ErrorText &= "  FromPosition must be a positive number between zero and one"
            End If
          Case "toPos"
            Dim toPos As Double = -1
            If (Not Double.TryParse(value, toPos)) OrElse toPos < 0 OrElse toPos > 1 Then
              row.ErrorText &= "  ToPosition must be a positive number between zero and one"
            End If
          Case Else
            Throw New Exception("Unexpected Column")
        End Select
      Next column

      ' Now, validate that the from position is always less than the two position
      ' FromPosition and ToPosition only matter for edge barriers
      If row.DataGridView.Name = "dataGridViewEdges" Then
        If row.Cells("FromPos").Value Is Nothing OrElse row.Cells("ToPos").Value Is Nothing Then
          row.ErrorText &= "  FromPosition and ToPosition must have valid decimal values between 0 and 1"
          Return
        End If

        Dim fromPos As Double = -1
        Double.TryParse(row.Cells("FromPos").Value.ToString(), fromPos)

        Dim toPos As Double = -1
        Double.TryParse(row.Cells("ToPos").Value.ToString(), toPos)

        If fromPos > toPos Then
          row.ErrorText &= "  FromPosition must be equal to or less than the ToPosition"
          Return
        End If
      End If
    End Sub

    ''' <summary>
    ''' Verify that the EID corresponds to a valid network element
    ''' <param name="value">The EID passed as a string</param>
    ''' <param name="elementType">The type of element to be verified</param>
    ''' </summary>
    Private Function ValidateEID(ByVal value As String, ByVal elementType As esriNetworkElementType) As Boolean
      ' validate that the EID is a valid integer
      Dim eid As Integer = -1
      If (Not Int32.TryParse(value, eid)) OrElse eid < 1 Then
        Return False
      End If

      ' QueryEdge and QueryJunction will throw exceptions if the EID doesn't match any elements
      Dim netQuery As INetworkQuery = TryCast(m_context.NetworkDataset, INetworkQuery)
      Try
        Select Case elementType
          Case esriNetworkElementType.esriNETJunction
            Dim junction As INetworkJunction = TryCast(netQuery.CreateNetworkElement(esriNetworkElementType.esriNETJunction), INetworkJunction)
            netQuery.QueryJunction(eid, junction)
          Case esriNetworkElementType.esriNETEdge
            Dim edge As INetworkEdge = TryCast(netQuery.CreateNetworkElement(esriNetworkElementType.esriNETEdge), INetworkEdge)
            netQuery.QueryEdge(eid, esriNetworkEdgeDirection.esriNEDAlongDigitized, edge)
          Case Else
            Return False
        End Select
      Catch
        Return False
      End Try

      Return True
    End Function

#End Region

#Region "Flash the Geometry"

    ''' <summary>
    ''' Occurs when a dataGrid row header is clicked.
    '''   It is used here to flash the geometry of the newly selected row
    ''' <param name="sender">The control raising this event</param>
    ''' <param name="e">Arguments associated with the event</param>
    ''' </summary>
    Private Sub dataGridView_RowHeaderMouseClick(ByVal sender As Object, ByVal e As DataGridViewCellMouseEventArgs) Handles dataGridViewJunctions.RowHeaderMouseClick, dataGridViewEdges.RowHeaderMouseClick
      ' make sure none of the cells are in edit mode.  When in edit mode, the values obtained
      '  programmatically will not yet match the value the user has changed the cell to
      Dim dgv As DataGridView = CType(sender, DataGridView)
      dgv.EndEdit()

      ' Only flash when there is one selected row
      If dgv.SelectedRows.Count > 1 Then
        Return
      End If
      Dim selectedRow As DataGridViewRow = dgv.SelectedRows(0)

      ' If it is the extra dataGrid row or has errors, then don't try to flash it 
      ValidateRow(selectedRow)
      If selectedRow.IsNewRow OrElse selectedRow.ErrorText <> "" Then
        Return
      End If

      ' also, if any of the row's cell have no value, then don't want to flash it
      For Each cell As DataGridViewCell In selectedRow.Cells
        If cell.Value Is Nothing Then
          Return
        End If
      Next cell

      ' use the EID to obtain the barrier's corresponding network element and source feature
      Dim element As INetworkElement = GetElementByEID(selectedRow.Cells(0).Value.ToString(), dgv.Name)
      If element Is Nothing Then
        Return
      End If
      Dim sourceFeature As IFeature = GetSourceFeature(element)

      ' For an edge, get the part geometry of the barrier covered portion of the source feature
      '  that should be displayed
      Dim netEdge As INetworkEdge = TryCast(element, INetworkEdge)
      Dim displayDirection As esriNetworkEdgeDirection = esriNetworkEdgeDirection.esriNEDNone
      If Not netEdge Is Nothing Then
        sourceFeature.Shape = GetBarrierSubcurve(netEdge, sourceFeature, selectedRow)
        displayDirection = GetDirectionValue(selectedRow)
      End If

      ' Draw
      FlashFeature(sourceFeature, displayDirection)
    End Sub

    ''' <summary>
    ''' Determine the esriNetworkEdgeDirection from the value in the dataGridView cell
    ''' <param name="selectedRow">The row containing the barrier's location range information</param>
    ''' </summary>
    Private Function GetDirectionValue(ByVal selectedRow As DataGridViewRow) As esriNetworkEdgeDirection
      Dim direction As esriNetworkEdgeDirection = esriNetworkEdgeDirection.esriNEDNone
      Dim textValue As String = selectedRow.Cells(1).Value.ToString()
      If textValue = EDGE_ALONG Then
        direction = esriNetworkEdgeDirection.esriNEDAlongDigitized
      ElseIf textValue = EDGE_AGAINST Then
        direction = esriNetworkEdgeDirection.esriNEDAgainstDigitized
      End If

      Return direction
    End Function

    ''' <summary>
    ''' Take a network edge, a source feature, and a row from the edges dataGrid, and determine
    '''  the geometry to be flashed on the map
    ''' <param name="netEdge">The edge upon which the barrier resides</param>
    ''' <param name="sourceFeature">The source feature corresponding to the network edge</param>
    ''' <param name="selectedRow">The row containing the barrier's location range information</param>
    ''' </summary>
    Private Function GetBarrierSubcurve(ByVal netEdge As INetworkEdge, ByVal sourceFeature As IFeature, ByVal selectedRow As DataGridViewRow) As ICurve
      ' value for displaying the entire source feature
      Dim fromPosition As Double = 0
      Dim toPosition As Double = 1

      ' Find the values for displaying only the element portion of the source feature
      Dim fromElementPosition, toElementPosition As Double
      netEdge.QueryPositions(fromElementPosition, toElementPosition)

      ' due to the element possibly being in the against digitized direction,
      '   fromPosition could be greater than toPosition.  If that is the case, swap the values
      If fromElementPosition > toElementPosition Then
        Dim tmp As Double = fromElementPosition
        fromElementPosition = toElementPosition
        toElementPosition = tmp
      End If

      Dim direction As esriNetworkEdgeDirection = GetDirectionValue(selectedRow)
      If direction = esriNetworkEdgeDirection.esriNEDNone Then
        Return Nothing
      End If

      ' Flash the edge
      If rbFlashElementPortion.Checked Then
        fromPosition = fromElementPosition
        toPosition = toElementPosition
        ' Flash the barrier portion of the edge
      ElseIf rbFlashBarrierPortion.Checked Then
        Dim fromBarrierPosition As Double = -1
        Dim toBarrierPosition As Double = -1

        ' gather the from and to position values for the barrier
        fromBarrierPosition = Double.Parse(selectedRow.Cells(2).Value.ToString())
        toBarrierPosition = Double.Parse(selectedRow.Cells(3).Value.ToString())

        ' for barriers in the against direction, we need to adjust that the element position is
        If direction = esriNetworkEdgeDirection.esriNEDAgainstDigitized Then
          fromBarrierPosition = 1 - fromBarrierPosition
          toBarrierPosition = 1 - toBarrierPosition
        End If

        ' use the positioning along the element of the barrier
        '  to get the position along the original source feature
        fromPosition = fromElementPosition + (fromBarrierPosition * (toElementPosition - fromElementPosition))
        toPosition = fromElementPosition + (toBarrierPosition * (toElementPosition - fromElementPosition))
      End If

      If fromPosition > toPosition Then
        Dim tmp As Double = fromPosition
        fromPosition = toPosition
        toPosition = tmp
      End If

      ' get the subspan on the polyline that represents the from and to positions we specified
      Dim displayCurve As ICurve = Nothing
      Dim sourceCurve As ICurve = TryCast(sourceFeature.Shape, ICurve)
      sourceCurve.GetSubcurve(fromPosition, toPosition, True, displayCurve)
      Return displayCurve
    End Function

    ''' <summary>
    ''' Take an EID value as a string from one of the dataGridView controls and find
    '''  the network element that corresponds to the EID
    ''' <param name="eidString">The EID value as a string</param>
    ''' <param name="datagridviewName">The name of the dataGrid that held the EID</param>
    ''' </summary>
    Private Function GetElementByEID(ByVal eidString As String, ByVal datagridviewName As String) As INetworkElement
      Dim eid As Integer = -1
      If (Not Int32.TryParse(eidString, eid)) Then
        Return Nothing
      End If

      Dim netQuery As INetworkQuery = TryCast(m_context.NetworkDataset, INetworkQuery)
      Dim edge As INetworkEdge = TryCast(netQuery.CreateNetworkElement(esriNetworkElementType.esriNETEdge), INetworkEdge)
      Dim junction As INetworkJunction = TryCast(netQuery.CreateNetworkElement(esriNetworkElementType.esriNETJunction), INetworkJunction)

      Dim element As INetworkElement = Nothing
      Try
        ' Populate the network element from the EID
        If datagridviewName = "dataGridViewEdges" Then
          netQuery.QueryEdge(eid, esriNetworkEdgeDirection.esriNEDAlongDigitized, edge)
          element = TryCast(edge, INetworkElement)
        ElseIf datagridviewName = "dataGridViewJunctions" Then
          netQuery.QueryJunction(eid, junction)
          element = TryCast(junction, INetworkElement)
        End If
      Catch
        ' if the query fails, the element will not be displayed
      End Try

      Return element
    End Function

    ''' <summary>
    ''' Take a network element and return its corresponding source feature
    ''' <param name="element">The return source feature corresponds to this element</param>
    ''' </summary>
    Private Function GetSourceFeature(ByVal element As INetworkElement) As IFeature
      ' To draw the network element, we will need its corresponding source feature information
      ' Get the sourceID and OID from the element
      Dim sourceID As Integer = element.SourceID
      Dim sourceOID As Integer = element.OID

      ' Get the source feature from the network source
      Dim netSource As INetworkSource = m_context.NetworkDataset.SourceByID(sourceID)
      Dim fClassContainer As IFeatureClassContainer = TryCast(m_context.NetworkDataset, IFeatureClassContainer)
      Dim sourceFClass As IFeatureClass = fClassContainer.ClassByName(netSource.Name)
      Return sourceFClass.GetFeature(sourceOID)
    End Function

    ''' <summary>
    ''' Flash the feature geometry on the map
    ''' <param name="pFeature">The feature being flashed</param>
    ''' <param name="pMxDoc">A hook to the application display</param>
    ''' <param name="direction">The digitized direction of the barrier with respect to the underlying source feature</param>
    ''' </summary>
    Private Sub FlashFeature(ByVal pFeature As IFeature, ByVal direction As esriNetworkEdgeDirection)
      Dim pMxDoc As IMxDocument = TryCast(m_app.Document, IMxDocument)

      ' Start drawing on screen. 
      pMxDoc.ActiveView.ScreenDisplay.StartDrawing(0, CShort(Fix(esriScreenCache.esriNoScreenCache)))

      ' Switch functions based on Geometry type. 
      Select Case pFeature.Shape.GeometryType
        Case esriGeometryType.esriGeometryPolyline
          FlashLine(pMxDoc.ActiveView.ScreenDisplay, pFeature.Shape, direction)
        Case esriGeometryType.esriGeometryPolygon
          ' no network elements can be polygons
        Case esriGeometryType.esriGeometryPoint
          FlashPoint(pMxDoc.ActiveView.ScreenDisplay, pFeature.Shape)
        Case Else
          Throw New Exception("Unexpected Geometry Type")
      End Select

      ' Finish drawing on screen. 
      pMxDoc.ActiveView.ScreenDisplay.FinishDrawing()
    End Sub

    ''' <summary>
    ''' Flash a line feature on the map
    ''' <param name="pDisplay">The map screen</param>
    ''' <param name="pGeometry">The geometry of the feature to be flashed</param>
    ''' <param name="direction">The digitized direction of the barrier with respect to the underlying source feature</param>
    ''' </summary>
    Private Sub FlashLine(ByVal pDisplay As IScreenDisplay, ByVal pGeometry As IGeometry, ByVal direction As esriNetworkEdgeDirection)
      ' The flash will be on a line symbol with an arrow on it
      Dim ipArrowLineSymbol As ICartographicLineSymbol = New CartographicLineSymbolClass()

      ' the line color will be red
      Dim ipRgbRedColor As IRgbColor = New RgbColorClass()
      ipRgbRedColor.Red = 192

      ' the arrow will be black
      Dim ipRgbBlackColor As IRgbColor = New RgbColorClass()
      ipRgbBlackColor.RGB = 0

      ' set up the arrow that will be displayed along the line
      Dim ipArrowMarker As IArrowMarkerSymbol = New ArrowMarkerSymbolClass()
      ipArrowMarker.Style = esriArrowMarkerStyle.esriAMSPlain
      ipArrowMarker.Length = 18
      ipArrowMarker.Width = 12
      ipArrowMarker.Color = ipRgbBlackColor

      ' set up the line itself
      ipArrowLineSymbol.Width = 4
      ipArrowLineSymbol.Color = ipRgbRedColor

      ' Set up the Raster Op-Code to help the flash mechanism
      CType(ipArrowMarker, ISymbol).ROP2 = esriRasterOpCode.esriROPNotXOrPen
      CType(ipArrowLineSymbol, ISymbol).ROP2 = esriRasterOpCode.esriROPNotXOrPen

      ' decorate the line with the arrow symbol
      Dim ipSimpleLineDecorationElement As ISimpleLineDecorationElement = New SimpleLineDecorationElementClass()
      ipSimpleLineDecorationElement.Rotate = True
      ipSimpleLineDecorationElement.PositionAsRatio = True
      ipSimpleLineDecorationElement.MarkerSymbol = ipArrowMarker
      ipSimpleLineDecorationElement.AddPosition(0.5)
      Dim ipLineDecoration As ILineDecoration = New LineDecorationClass()
      ipLineDecoration.AddElement(ipSimpleLineDecorationElement)
      CType(ipArrowLineSymbol, ILineProperties).LineDecoration = ipLineDecoration

      ' the arrow is initially set to correspond to the digitized direction of the line
      '  if the barrier direction is against digitized, then we need to flip the arrow direction
      If direction = esriNetworkEdgeDirection.esriNEDAgainstDigitized Then
        ipSimpleLineDecorationElement.FlipAll = True
      End If

      ' Flash the line
      '  Two calls are made to Draw.  Since the ROP2 setting is NotXOrPen, the first call
      '  draws the symbol with our new symbology and the second call redraws what was originally 
      '  in the place of the symbol
      pDisplay.SetSymbol(TryCast(ipArrowLineSymbol, ISymbol))
      pDisplay.DrawPolyline(pGeometry)
      System.Threading.Thread.Sleep(300)
      pDisplay.DrawPolyline(pGeometry)
    End Sub

    ''' <summary>
    ''' Flash a point feature on the map
    ''' <param name="pDisplay">The map screen</param>
    ''' <param name="pGeometry">The geometry of the feature to be flashed</param>
    ''' </summary>
    Private Sub FlashPoint(ByVal pDisplay As IScreenDisplay, ByVal pGeometry As IGeometry)
      ' for a point, we only flash a simple circle
      Dim pMarkerSymbol As ISimpleMarkerSymbol = New SimpleMarkerSymbolClass()
      pMarkerSymbol.Style = esriSimpleMarkerStyle.esriSMSCircle

      ' Set up the Raster Op-Code to help the flash mechanism
      Dim pSymbol As ISymbol = TryCast(pMarkerSymbol, ISymbol)
      pSymbol.ROP2 = esriRasterOpCode.esriROPNotXOrPen

      ' Flash the point
      '  Two calls are made to Draw.  Since the ROP2 setting is NotXOrPen, the first call
      '  draws the symbol with our new symbology and the second call redraws what was originally 
      '  in the place of the symbol
      pDisplay.SetSymbol(pSymbol)
      pDisplay.DrawPoint(pGeometry)
      System.Threading.Thread.Sleep(300)
      pDisplay.DrawPoint(pGeometry)
    End Sub

#End Region
  End Class
End Namespace