dot
Using Bing Imagery, Geocode, and Route services


Bing Maps hosts rich map imagery and data as well as robust search, location, and routing services. The ArcGIS API for Microsoft Silverlight/WPF integrates Bing Maps capabilities and data in a separate assembly: ESRI.ArcGIS.Client.Bing.dll. Once you have a Bing Maps developer account, you can begin working with Bing services in your application.

See the Bing Maps sample which contains the code and implementation techniques presented in this document.

Working with the Imagery service

The Bing imagery service provide access to cached tiles of map data available in the Web Mercator projection (WKID 102113). The ArcGIS API for Microsoft Silverlight/WPF includes Bing imagery service support in a TileLayer component which can be added to a Map's layer collection. The following example shows the XAML markup for a simple Silverlight application that contains an ArcGIS Silverlight API Map control and a single Bing map layer. Use the Creating a map topic for information on how to create a map and reference it's layer collection. The Bing TileLayer class is included in the ESRI.ArcGIS.Client.Bing.dll in the ESRI.ArcGIS.Client.Bing namespace.

[XAML]

<UserControl x:Class="SilverlightApp.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:esri="clr-namespace:ESRI.ArcGIS.Client;assembly=ESRI.ArcGIS.Client"
xmlns:bing="clr-namespace:ESRI.ArcGIS.Client.Bing;assembly=ESRI.ArcGIS.Client.Bing">
<Grid x:Name="LayoutRoot" Background="White">

<esri:Map x:Name="MyMap" Loaded="MyMap_Loaded">
<esri:Map.Layers>
<bing:TileLayer ID="BingLayerRoad" LayerStyle="Road" ServerType="Staging" />
</esri:Map.Layers>
</esri:Map>

</Grid>
</UserControl>

The ID attribute must be set to a unique value and will be used to access the layer in code-behind to define the token. The LayerStyle attribute can be set to one of three options: Road, Aerial, and AerialWithLabels. The ServerType attributes defines your access license to Bing services: Staging (free) or Production (license fee). When ServerType is set to Staging, all map tiles contain the text "Staging".

Before the layer can be used in the Map at runtime, a valid token from the Bing token service must be applied. While this can be defined as an attribute in markup, it would require you to recompiled the application when the token expired (maximum 8 hours). The more flexible and functional option involves generating the token programmatically when the application starts. The Using a Bing token service topic discusses this solution in more detail. In general, you need to set the token on the TileLayer before the layer initializes. In the code-behind example below, the token is applied after the Map is loaded (handle in the Map.Loaded event). The BingToken property is a user-defined property in the Application page code-behind and set when the application first loads.

[C#]

private void MyMap_Loaded(object sender, RoutedEventArgs e)
{
foreach (Layer layer in MyMap.Layers)
if (layer is TileLayer)
(layer as TileLayer).Token = (Application.Current as BingApp.App).BingToken;
. . .


Working with the Geocode service

The Bing geocode service provides world-wide coverage for matching addresses, places, and landmarks to locations on the globe, as well as returning information for a specified location. Locations are returned in a geographic coordinate system (WKID 4326). The ArcGIS API for Microsoft Silverlight/WPF includes a Geocoder class to manage interaction with the Bing geocode service. The class is not represented in XAML, instead it is always created and used in the code-behind. The code below shows how to create a Geocoder, specify the Bing token with the constructor, and call the appropriate method for the operation: Geocode or ReverseGeocode. The event handler to process results from either operation is defined as a method parameter (in the example below, MyGeocode_Complete and MyReverseGeocode_Complete, respectively).

[C#]

Geocoder geocoder = new Geocoder(BingToken);
geocoder.ServerType = ServerType.Production;

// Geocode
geocoder.Geocode(MyAddress, MyGeocode_Complete);

// Reverse Geocode geocoder.ReverseGeocode(MyMapPoint, MyReverseGeocode_Complete);

Geocoding

Input to a geocode operation is comma delimited and can contain a street address (e.g. "901 N 1st Ave, Tucson, AZ"), place name, (e.g. "Annapolis, MD", or a landmark name (e.g. "Prospect Park"). All inputs will be used to determine the location with the best match. For example, "Big Ben" will return a location in London, UK whereas "Big Ben, US" will return locations in the United States that contain "Big Ben" in their name, such as Big Bend Park, OR. Bing geocode operations use a confidence value to determine how confident the service is in the results returned to the client. The Geocoder uses the highest minimum confidence value permitted by the Bing geocode service - which means only most accurate matches will be returned.

Results from a geocode operation are returned in a GeocodeResponse, a class defined by the Bing Web Services SDK. Each response may contain one or more GeocodeResult instances, one for each match. The GeocodeResult contains the name or address of the match location, a bounding box, administrative type description, etc. You can iterate through each GeocodeResult instance to create graphics for display in an ArcGIS Silverlight Map control. Note, locations are returned in the geographic coordinate system WGS84 (WKID: 4326). A Transform utility class is included with the Bing implementation of the ArcGIS Silverlight API to convert between geographic (WKID: 4326) and Web Mercator (WKID: 102113). The conversion is managed completely on the client. To transform geometry to or from a different coordinate system you'll need to use another resource (e.g. use an ArcGIS Server Geometry service via a Geometry task). The following code example demonstrates how to add results to a graphics layer displayed on a Bing basemap:

[C#]

        private void Geocode_Complete(object sender, GeocodeCompletedEventArgs args)
        {
            GeocodeResponse geocodeResponse = args.Result;

            if (geocodeResponse.Results.Count == 0)
                return;

            GraphicsLayer graphicsLayer = MyMap.Layers["GeocodeResultsGraphicsLayer"] as GraphicsLayer;

            foreach (GeocodeResult geocodeResult in geocodeResponse.Results)
            {
                ESRI.ArcGIS.Client.Graphic graphic = new ESRI.ArcGIS.Client.Graphic();

                //Optional, transform point from geographic to the Web Mercator projection the current map uses
                MapPoint geographicPoint = new MapPoint(geocodeResult.Locations[0].Longitude, geocodeResult.Locations[0].Latitude,
                    new SpatialReference(4326));

                graphic.Geometry = ESRI.ArcGIS.Client.Bing.Transform.GeographicToWebMercator(geographicPoint);
                graphic.Symbol = CircleSymbol;
                graphic.Attributes.Add("DisplayName", geocodeResult.DisplayName);

                graphicsLayer.Graphics.Add(graphic);
            }
        }

Reverse Geocoding

Input to a reverse geocode operation is a map location used to match known geographic entities. Currently, only street addresses are returned. With regard to the Geocoder class, the location is a MapPoint with a spatial reference in geographic (WKID: 4326) or Web Mercator (WKID: 102113).

Results from a reverse geocode operation are returned in a GeocodeResponse, a class defined by the Bing Web Services SDK. Each response may contain one or more GeocodeResult instances, one for each match. In most cases you'll be interested in the closest street address and actual match location, both available in the GeocodeResult. The following code example demonstrates how to retrieve the street address (display name) of the first match result and show it in a message box.

[C#]

        private void ReverseGeocode_Complete(object sender, ReverseGeocodeCompletedEventArgs args)
        {
            string response;
            if (args.Result.Results.Count == 0)
                response = "No results found";
            else
            {
                GeocodeResult result = args.Result.Results[0];
                response = result.DisplayName;
            }
            MessageBox.Show(response);
        }


Working with the Route service

The Bing route service provides route calculation and itinerary creation between specified locations. Routes are returned as a set of coordinates in a geographic coordinate system (WKID 4326). The ArcGIS API for Microsoft Silverlight/WPF includes a Routing class to manage interaction with the Bing route service. As with the Geocoder, the class is not represented in XAML, but rather is always created and used in the code-behind.

Routing

Input to a routing operation consists of the stops to be visited. Each stop must be specified as a MapPoint with a SpatialReference of Web Mercator (WKID 102113) or WGS 1984 (WKID 4326) and included in a collection that implements IEnumerable (e.g. a List). The route returned will pass through stops in the order they were specified. Furthermore, the route service offers options for optimizing by travel time or distance, accounting for traffic, and getting driving or walking directions. These options are made available as properties of the Routing class.

The code below shows how to create a Routing instance, specify the Bing token with the constructor, specify routing options, and execute the operation. The event handler to process results is defined as a method parameter (in the example below, MyRoute_Complete).

[C#]

// Instantiate Routing class and specify server type (production or staging)
Routing routing = new Routing(BingToken);
routing.ServerType = ServerType.Production;

// Set routing options
routing.Optimization = RouteOptimization.MinimizeTime;
routing.TrafficUsage = TrafficUsage.None;
routing.TravelMode = TravelMode.Driving;

// Execute routing operation
routing.Route(WayPoints, MyRoute_Complete);

Results from a route operation are returned in a RouteResponse, which is a class defined by the Bing Web Services SDK. Each response contains one RouteResult instance. The RouteResult contains the coordinates of the points along the calculated route in a RoutePath, the route's distance and travel time in a RouteSummary, and the description, distance, and travel time for each leg of the route in an array of RouteLegs. To display the route in an ArcGIS Silverlight Map control, you can iterate through each point of the RoutePath instance to create a Polyline geometry, which can then be wrapped in a Graphic. Note that locations along the route are returned in the geographic coordinate system WGS84 (WKID: 4326). A Transform utility class is included with the Bing implementation of the ArcGIS Silverlight API to convert between geographic (WKID: 4326) and Web Mercator (WKID: 102113). The conversion is managed completely on the client. To transform geometry to or from a different coordinate system you'll need to use another resource (e.g. use an ArcGIS Server Geometry service via a Geometry task). The following code example demonstrates how to add a route to a graphics layer displayed on a Bing basemap:

[C#]

private void MyRoute_Complete(object sender, CalculateRouteCompletedEventArgs args)
{
    // Get the coordinates along the route
    RoutePath routePath = args.Result.Result.RoutePath;

    // Instantiate a Polyline to hold the route geometry
    Polyline line = new Polyline();
    line.Paths.Add(new PointCollection());

    // Traverse route coordinate pairs
    foreach (ESRI.ArcGIS.Client.Bing.RouteService.Location location in routePath.Points)
    {
        // Create a point from the coordinates
        MapPoint routePoint = new MapPoint(location.Longitude, location.Latitude);
        // Transform the point to Web Mercator and add to the route polyline.
        // The tranformation only applies when using a Web Mercator base map.
        line.Paths[0].Add(Transform.GeographicToWebMercator(routePoint));
    }

    // Wrap the route in a graphic and add it to a graphics layer
    Graphic graphic = new Graphic()
    {
        Geometry = line,
        Symbol = RoutePathSymbol
    };
    GraphicsLayer graphicsLayer = MyMap.Layers["RouteResultsGraphicsLayer"] as GraphicsLayer;
    graphicsLayer.Graphics.Add(graphic);
}

To generate directions, you can traverse each ItineraryItem contained by the Itinerary property of each RouteLeg included with the results. This technique is shown below. In this case, once a string containing the directions is initialized, the directionas are displayed by passing that string to a TextBlock. This of course assumes that the TextBlock is visible somewhere within the application.

[C#]

private void MyRoute_Complete(object sender, CalculateRouteCompletedEventArgs args)
{
    // Get the coordinates along the route
    RoutePath routePath = args.Result.Result.RoutePath;

    // Instantiate a Polyline to hold the route geometry
    Polyline line = new Polyline();
    line.Paths.Add(new PointCollection());

    // Traverse route coordinate pairs
    foreach (ESRI.ArcGIS.Client.Bing.RouteService.Location location in routePath.Points)
    {
        // Create a point from the coordinates
        MapPoint routePoint = new MapPoint(location.Longitude, location.Latitude);
        // Transform the point to Web Mercator and add to the route polyline.
        // The tranformation only applies when using a Web Mercator base map.
        line.Paths[0].Add(Transform.GeographicToWebMercator(routePoint));
    }

    // Wrap the route in a graphic and add it to a graphics layer
    Graphic graphic = new Graphic()
    {
        Geometry = line,
        Symbol = RoutePathSymbol
    };
    GraphicsLayer graphicsLayer = MyMap.Layers["RouteResultsGraphicsLayer"] as GraphicsLayer;
    graphicsLayer.Graphics.Add(graphic);

    // Get the legs of the route
    ObservableCollection routeLegs = args.Result.Result.Legs;
    
    StringBuilder directions = new StringBuilder();
    int instructionCount = 1;
    // Traverse the legs, extracting the directions from each
    for (int i = 0; i < routeLegs.Count; i++)
    {
        // Write a header for the current leg
        directions.Append(string.Format("--Leg #{0}--\n", i + 1));

        // Iterate over the items in the current leg
        foreach (ItineraryItem item in routeLegs[i].Itinerary)
        {
            // Write the description of the current step
            directions.Append(string.Format("{0}. {1}\n", instructionCount, item.Text));
            instructionCount++;
        }
    }

    // Update the UI with the directions
    DirectionsTextBlock.Text = directions.ToString();
}