ArcGIS Select Buffer Tool
ArcGIS_SelectBufferTool_VBNet\App_Code\Utility.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 Microsoft.VisualBasic
Imports System
Public Class Utility
  ''' <summary>
  ''' Retrieves the well-known ID of a unit based on its name.  These names are 
  ''' taken from the ESRI.ArcGIS.ADF.Web.DataSources.Units enumeration.
  ''' </summary>
  ''' <param name="unitName">Name of the unit to retrieve the WKID for</param>
  Public Shared Function GetWkidByUnitName(ByVal unitName As String) As Integer
    Dim wkid As Integer = -1
    Select Case unitName
      Case "Inches" 
        wkid = 109009
      Case "Feet" 
        wkid = 9003
      Case "Yards" 
        wkid = 109002
      Case "Miles" 
        wkid = 9035
      Case "NauticalMiles" 
        wkid = 9030
      Case "Millimeters" 
        wkid = 109007
      Case "Centimeters" 
        wkid = 109006
      Case "Meters" 
        wkid = 9001
      Case "Kilometers" 
        wkid = 9036
      Case "Decimeters" 
        wkid = 109005
    End Select

    Return wkid
  End Function

  ''' <summary>
  ''' Gets the minimum enclosing rectangle (i.e. bounding box) of the passed-in polygon
  ''' </summary>
  ''' <param name="agsSoapPolygon">Polygon to retrieve the MER for</param>
  Public Shared Function GetBoundingExtent(ByVal agsSoapPolygon As ESRI.ArcGIS.ADF.ArcGISServer.PolygonN) As ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN
    ' Instantiate an envelope with max values minimized and min values maximized
    Dim agsSoapBoundingBox As ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN = New ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN()
    agsSoapBoundingBox.XMin = Double.MaxValue
    agsSoapBoundingBox.XMax = Double.MinValue
    agsSoapBoundingBox.YMin = Double.MaxValue
    agsSoapBoundingBox.YMax = Double.MinValue

    ' Iterate through all the polygon's vertices
    Dim i As Integer = 0
    Do While i < agsSoapPolygon.RingArray.Length
      Dim agsSoapRing As ESRI.ArcGIS.ADF.ArcGISServer.Ring = agsSoapPolygon.RingArray(i)
      Dim j As Integer = 0
      Do While j < agsSoapRing.PointArray.Length
        ' For each vertex, expand the bounds of the minimum enclosing rectangle with the 
        ' vertex's coordinates if they fall outside the rectangle's current bounds
        Dim agsSoapCurrentPoint As ESRI.ArcGIS.ADF.ArcGISServer.PointN = TryCast(agsSoapRing.PointArray(j), ESRI.ArcGIS.ADF.ArcGISServer.PointN)

        If agsSoapCurrentPoint.X < agsSoapBoundingBox.XMin Then
          agsSoapBoundingBox.XMin = agsSoapCurrentPoint.X
        ElseIf agsSoapCurrentPoint.X > agsSoapBoundingBox.XMax Then
          agsSoapBoundingBox.XMax = agsSoapCurrentPoint.X
        End If

        If agsSoapCurrentPoint.Y < agsSoapBoundingBox.YMin Then
          agsSoapBoundingBox.YMin = agsSoapCurrentPoint.Y
        ElseIf agsSoapCurrentPoint.Y > agsSoapBoundingBox.YMax Then
          agsSoapBoundingBox.YMax = agsSoapCurrentPoint.Y
        End If
        j += 1
      Loop
      i += 1
    Loop

    Return agsSoapBoundingBox
  End Function

  ''' <summary>
  ''' Creates a spatial reference that will minimize distortion near the input polygon.  Ideal for operations 
  ''' that will derive geometry based on that polygon (i.e. buffer).
  ''' </summary>
  ''' <param name="agsSoapPolygon">Polygon to use as the center of the spatial reference's datum</param>
  ''' <param name="geometryServerProxy">Geometry service to use in creating the spatial reference</param>
  Public Shared Function CreateOperationSpatialReference(ByVal agsSoapPolygon As ESRI.ArcGIS.ADF.ArcGISServer.PolygonN, ByVal geometryServerProxy As ESRI.ArcGIS.ADF.ArcGISServer.GeometryServerProxy) As ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference
    ' Get the polygon's minimum enclosing rectangle (MER)
    Dim agsSoapBoundingEnvelope As ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN = GetBoundingExtent(agsSoapPolygon)

    ' If the input polygon's spatial reference uses a projected coordinate system, project the MER to a 
    ' geographic coordinate system (WGS 1984 in this case).  We do this because the MER's coordinates 
    ' will be used to initialize the datum of the operation spatial reference
    If TypeOf agsSoapPolygon.SpatialReference Is ESRI.ArcGIS.ADF.ArcGISServer.ProjectedCoordinateSystem Then
      ' Create an ArcGIS Server spatial reference initalized to use the WGS 1984 coordinate system
      Dim agsSoapGeographicSpatialReference As ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference = New ESRI.ArcGIS.ADF.ArcGISServer.GeographicCoordinateSystem()
      agsSoapGeographicSpatialReference.WKID = 4326
      agsSoapGeographicSpatialReference.WKIDSpecified = True

      ' Place the input MER in an array for the project operation
      Dim agsSoapInputGeometryArray As ESRI.ArcGIS.ADF.ArcGISServer.Geometry() = New ESRI.ArcGIS.ADF.ArcGISServer.Geometry(0){}
      agsSoapInputGeometryArray(0) = agsSoapBoundingEnvelope

      ' Execute the projection
      Dim agsSoapOutputGeometryArray As ESRI.ArcGIS.ADF.ArcGISServer.Geometry() = geometryServerProxy.Project(agsSoapPolygon.SpatialReference, agsSoapGeographicSpatialReference, True, Nothing, Nothing, agsSoapInputGeometryArray)

      ' Retrieve the projected MER from the results array
      agsSoapBoundingEnvelope = TryCast(agsSoapOutputGeometryArray(0), ESRI.ArcGIS.ADF.ArcGISServer.EnvelopeN)
    End If

    ' Get the latitude (Y coordinate) at the center of the MER
    Dim centerLatitude As Double = agsSoapBoundingEnvelope.YMax - (agsSoapBoundingEnvelope.YMax - agsSoapBoundingEnvelope.YMin) / 2

    ' Create the definition string for the operation spatial reference's coordinate system.  We will use 
    ' the Hotine Oblique Mercator coordinate system because it lends itself well to minimizing operational
    ' distortion anywhere on the earth
    Dim hotineObliqueMercatorDefinition As String = "" & ControlChars.CrLf & "            PROJCS[""World_Hotine""," & ControlChars.CrLf & "            GEOGCS[""GCS_WGS_1984""," & ControlChars.CrLf & "            DATUM[""D_WGS_1984""," & ControlChars.CrLf & "            SPHEROID[""WGS_1984"",6378137.0,298.257223563]]," & ControlChars.CrLf & "            PRIMEM[""Greenwich"",0.0]," & ControlChars.CrLf & "            UNIT[""Degree"",0.0174532925199433]]," & ControlChars.CrLf & "            PROJECTION[""Hotine_Oblique_Mercator_Two_Point_Natural_Origin""]," & ControlChars.CrLf & "            PARAMETER[""False_Easting"",0.0]," & ControlChars.CrLf & "            PARAMETER[""False_Northing"",0.0]," & ControlChars.CrLf & "            PARAMETER[""Latitude_Of_1st_Point"",{0}],   " & ControlChars.CrLf & "            PARAMETER[""Latitude_Of_2nd_Point"",{1}]," & ControlChars.CrLf & "            PARAMETER[""Scale_Factor"",1.0]," & ControlChars.CrLf & "            PARAMETER[""Longitude_Of_1st_Point"",{2}]," & ControlChars.CrLf & "            PARAMETER[""Longitude_Of_2nd_Point"",{3}]," & ControlChars.CrLf & "            PARAMETER[""Latitude_Of_Center"",{4}]," & ControlChars.CrLf & "            UNIT[""Meter"",1.0]]"

    ' Specify the relevant coordinates of the MER for the coordinate system's datum parameters
    Dim customHotineObliqueCylindricalMercator As String = String.Format(hotineObliqueMercatorDefinition, agsSoapBoundingEnvelope.YMin, agsSoapBoundingEnvelope.YMax, agsSoapBoundingEnvelope.XMin, agsSoapBoundingEnvelope.XMax, centerLatitude)

    ' Create the spatial reference
    Dim agsSoapBufferSpatialReference As ESRI.ArcGIS.ADF.ArcGISServer.SpatialReference = geometryServerProxy.FindSRByWKT(customHotineObliqueCylindricalMercator, Nothing, True, True)

    Return agsSoapBufferSpatialReference
  End Function

  ''' <summary>
  ''' Creates an ArcGIS Server simple fill symbol with a solid outline and other parameters as
  ''' specified
  ''' </summary>
  ''' <param name="fillColor">Fill color of the symbol</param>
  ''' <param name="fillStyle">Fill style of the symbol</param>
  ''' <param name="outlineColor">Outline color of the symbol</param>
  ''' <param name="outlineWidth">Outline width of the symbol</param>
  Public Shared Function CreateSimpleFillSymbol(ByVal fillColor As System.Drawing.Color, ByVal fillStyle As ESRI.ArcGIS.ADF.ArcGISServer.esriSimpleFillStyle, ByVal outlineColor As System.Drawing.Color, ByVal outlineWidth As Integer) As ESRI.ArcGIS.ADF.ArcGISServer.SimpleFillSymbol
    ' Create an ArcGIS Server color object for the fill symbol based on the passed-in color
    Dim agsSoapFillColor As ESRI.ArcGIS.ADF.ArcGISServer.RgbColor = New ESRI.ArcGIS.ADF.ArcGISServer.RgbColor()
    agsSoapFillColor.Red = fillColor.R
    agsSoapFillColor.Green = fillColor.G
    agsSoapFillColor.Blue = fillColor.B

    ' Create a simple fill symbol with the color and passed-in fill style
    Dim agsSoapSimpleFillSymbol As ESRI.ArcGIS.ADF.ArcGISServer.SimpleFillSymbol = New ESRI.ArcGIS.ADF.ArcGISServer.SimpleFillSymbol()
    agsSoapSimpleFillSymbol.Style = fillStyle
    agsSoapSimpleFillSymbol.Color = agsSoapFillColor

    ' Create an ArcGIS Server color object for the outline
    Dim agsSoapOutlineColor As ESRI.ArcGIS.ADF.ArcGISServer.RgbColor = New ESRI.ArcGIS.ADF.ArcGISServer.RgbColor()
    agsSoapOutlineColor.Red = outlineColor.R
    agsSoapOutlineColor.Green = outlineColor.G
    agsSoapOutlineColor.Blue = outlineColor.B

    ' Create a simple line symbol with the color and passed-in width
    Dim agsSoapBufferSimpleLineSymbol As ESRI.ArcGIS.ADF.ArcGISServer.SimpleLineSymbol = New ESRI.ArcGIS.ADF.ArcGISServer.SimpleLineSymbol()
    agsSoapBufferSimpleLineSymbol.Color = agsSoapOutlineColor
    agsSoapBufferSimpleLineSymbol.Style = ESRI.ArcGIS.ADF.ArcGISServer.esriSimpleLineStyle.esriSLSSolid
    agsSoapBufferSimpleLineSymbol.Width = outlineWidth

    ' Apply the outline to the fill symbol
    agsSoapSimpleFillSymbol.Outline = agsSoapBufferSimpleLineSymbol

    Return agsSoapSimpleFillSymbol
  End Function

  ''' <summary>
  ''' Selects features that intersect the passed-in geometry.
  ''' </summary>
  ''' <param name="agsMapFunctionality">The map functionality of the resource containing the layer to 
  ''' select features from</param>
  ''' <param name="agsSoapIntersectGeometry">The geometry used for selecting features</param>
  ''' <param name="targetLayerName">The name of the layer to select features from</param>
  ''' <param name="selectionColor">The color to use in displaying the selected features</param>
  Public Shared Sub SelectIntersectingFeatures(ByVal agsMapFunctionality As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapFunctionality, ByVal agsSoapIntersectGeometry As ESRI.ArcGIS.ADF.ArcGISServer.Geometry, ByVal targetLayerName As String, ByVal selectionColor As System.Drawing.Color)
    ' Retrieve the index of the target layer
    Dim layerIDs As String() = Nothing
    Dim layerNames As String() = Nothing
    agsMapFunctionality.GetLayers(layerIDs, layerNames)

    Dim targetLayerIndex As Integer = 0
    Dim i As Integer = 0
    Do While i < layerNames.Length
      If layerNames(i) = targetLayerName Then
        targetLayerIndex = i
        Exit Do
      End If
      i += 1
    Loop

    ' Get a reference to the ArcGIS Server resource underlying the map functionality
    Dim mapResourceBase As ESRI.ArcGIS.ADF.Web.DataSources.ArcGISServer.MapResourceBase = agsMapFunctionality.MapResource

    ' Get MapLayerInfo object for the selection layer and use it to determine the name of the layer's
    ' geometry field.
    Dim agsSoapMapLayerInfoArray As ESRI.ArcGIS.ADF.ArcGISServer.MapLayerInfo() = mapResourceBase.MapServerInfo.MapLayerInfos
    Dim agsSoapActiveMapLayerInfo As ESRI.ArcGIS.ADF.ArcGISServer.MapLayerInfo = agsSoapMapLayerInfoArray(targetLayerIndex)

    Dim geometryFieldName As String = Nothing
    For Each agsSoapField As ESRI.ArcGIS.ADF.ArcGISServer.Field In agsSoapActiveMapLayerInfo.Fields.FieldArray
      If agsSoapField.Type = ESRI.ArcGIS.ADF.ArcGISServer.esriFieldType.esriFieldTypeGeometry Then
        geometryFieldName = agsSoapField.Name
        Exit For
      End If
    Next agsSoapField

    ' Initialize a spatial filter to use in selecting features that intersect the buffer
    Dim agsSoapSpatialFilter As ESRI.ArcGIS.ADF.ArcGISServer.SpatialFilter = New ESRI.ArcGIS.ADF.ArcGISServer.SpatialFilter()
    agsSoapSpatialFilter.FilterGeometry = agsSoapIntersectGeometry

    agsSoapSpatialFilter.GeometryFieldName = geometryFieldName
    agsSoapSpatialFilter.SpatialRel = ESRI.ArcGIS.ADF.ArcGISServer.esriSpatialRelEnum.esriSpatialRelIntersects

    ' Retrieve the layer's LayerDescription to use in specifying the layer ID for the query operation
    Dim agsSoapMapDescription As ESRI.ArcGIS.ADF.ArcGISServer.MapDescription = agsMapFunctionality.MapDescription
    Dim agsSoapLayerDescriptionArray As ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription() = agsSoapMapDescription.LayerDescriptions
    Dim agsSoapActiveLayerDescription As ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription = agsSoapLayerDescriptionArray(targetLayerIndex)

    ' Execute a query to retrieve the IDs of features that intersect the buffer
    Dim agsSoapMapServerProxy As ESRI.ArcGIS.ADF.ArcGISServer.MapServerProxy = mapResourceBase.MapServerProxy
    Dim selectionAgsSoapFIDSet As ESRI.ArcGIS.ADF.ArcGISServer.FIDSet = agsSoapMapServerProxy.QueryFeatureIDs(agsSoapMapDescription.Name, agsSoapActiveLayerDescription.LayerID, agsSoapSpatialFilter)

    ' Set the selection layer's selected features to those intersecting the buffer
    agsSoapActiveLayerDescription.SelectionFeatures = selectionAgsSoapFIDSet.FIDArray

    ' Create an ArcGIS Server color object from the passed-in color
    Dim selectionAgsSoapRgbColor As ESRI.ArcGIS.ADF.ArcGISServer.RgbColor = New ESRI.ArcGIS.ADF.ArcGISServer.RgbColor()
    selectionAgsSoapRgbColor.Red = selectionColor.R
    selectionAgsSoapRgbColor.Green = selectionColor.G
    selectionAgsSoapRgbColor.Blue = selectionColor.B

    ' Set the layer's selection color to the passed-in color
    agsSoapActiveLayerDescription.SelectionColor = selectionAgsSoapRgbColor
  End Sub

  ''' <summary>
  ''' Constructs a callback result that will display a javascript alert with an error message 
  ''' based on the passed-in exception
  ''' </summary>
  ''' <param name="exception">The exception from which the error message will be derived</param>
  ''' <returns></returns>
  Public Shared Function CreateErrorCallbackResult(ByVal exception As System.Exception) As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult
    ' Create a callback result to display an error message
    Dim jsAlertErrorMessage As String = GetJavaScriptErrorString(exception)
    Dim alertCallbackResult As ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult = ESRI.ArcGIS.ADF.Web.UI.WebControls.CallbackResult.CreateJavaScript(jsAlertErrorMessage)
    Return alertCallbackResult
  End Function

  ''' <summary>
  ''' Constructs the syntax to display a javascript alert with an error message based on the 
  ''' passed-in exception
  ''' </summary>
  ''' <param name="exception">The exception from which the error message will be derived</param>
  ''' <returns></returns>
  Public Shared Function GetJavaScriptErrorString(ByVal exception As System.Exception) As String
    ' Get the website's configuration file
    Dim webConfig As System.Configuration.Configuration = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(System.Web.HttpContext.Current.Request.ApplicationPath)

    ' Get the "compilation" section of the config file
    Dim compilationSection As System.Web.Configuration.CompilationSection = TryCast(webConfig.GetSection("system.web/compilation"), System.Web.Configuration.CompilationSection)

    ' If the config file's compilation section specifies debug mode, include 
    ' stack trace information in the error message.  Otherwise, just return 
    ' the exception message.
    Dim errorMessage As String = Nothing
    If (Not compilationSection Is Nothing) AndAlso (compilationSection.Debug) Then
      Dim stackTrace As String = exception.StackTrace.Replace("\", "\\")
      stackTrace = stackTrace.Replace(Constants.vbLf, "\n")
      stackTrace = stackTrace.Replace(Constants.vbCr, "\r")
      stackTrace = stackTrace.Replace("'", "\'")
      errorMessage = exception.Message.Replace("\", "\\")
      errorMessage = errorMessage.Replace(Constants.vbLf, "\n")
      errorMessage = errorMessage.Replace(Constants.vbCr, "\r")
      errorMessage = errorMessage.Replace("'", "\'")

      errorMessage = errorMessage & "\n\n" & stackTrace.Trim()
    Else
      errorMessage = exception.Message
    End If

    ' Create a callback result to display an error message
    Dim jsAlertException As String = "alert('" & errorMessage & "')"
    Return jsAlertException
  End Function
End Class