Using Bing Maps 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 Silverlight integrates Bing Maps capabilities and data in a separate assembly: ESRI.ArcGIS.Client.Bing.dll. Once you have a Bing Maps Key, you can begin working with Bing services in your application. See the Bing Maps samples in the sample code and implementation techniques presented in this document.

Working with the Imagery service

The Bing imagery service provides access to cached tiles of map data available in the Web Mercator projection (WKID: 102113). The ArcGIS API for Silverlight 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 for the main Grid of a simple Silverlight application that contains an ArcGIS API for Silverlight Map control and a single Bing Maps layer. See Creating a map for information on how to create a map and reference its layer collection. The Bing TileLayer class is included in the ESRI.ArcGIS.Client.Bing.dll in the ESRI.ArcGIS.Client.Bing namespace.

<Grid x:Name="LayoutRoot" Background="White">
  <esri:Map x:Name="MyMap" Loaded="MyMap_Loaded">
    <esri:Map.Layers>
      <bing:TileLayer ID="BingLayerRoad" LayerStyle="Road" ServerType="Production"
        Token="this-is-my-BingMapsKey" />
    </esri:Map.Layers>
  </esri:Map>
</Grid>
Note:

The code in this topic requires two XML namespace declarations: one mapping the esri to the ArcGIS schema for Silverlight, and one mapping to the ESRI.ArcGIS.Client.Bing namespace in the ESRI.ArcGIS.Client.Bing assembly.

xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
xmlns:bing="clr-namespace:ESRI.ArcGIS.Client.Bing;assembly=ESRI.ArcGIS.Client.Bing"

Caution:

You need to provide your Bing Maps key in the Token attribute—the code will not run successfully with the placeholder key used.

The ID attribute must be set to a unique value and will be used to access the layer in the 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 or Production. Bing Maps keys only work with Production services.

Before the layer can be used in the Map at runtime, a valid Bing Maps key must be generated and applied. See the Using Bing Maps overview for instructions on how to generate a key. Once you have a key, use it to set the Token property on the TileLayer. In general, you need to set the token on the TileLayer before the layer initializes. In the following code-behind example, 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's code-behind and set when the application first loads.

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 (GCS) World Geodetic System 1984 ([WGS84] WKID: 4326).

Caution:

Microsoft's Bing Maps API Terms of Use specify that all Bing Maps Geocode service results must be displayed on a Bing Maps basemap.

The ArcGIS API for Silverlight 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 following code 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 following example, MyGeocode_Complete and MyReverseGeocode_Complete, respectively).

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 (for example, 901 N 1st Ave, Tucson, AZ), place name, (for example, Annapolis, MD, or a landmark name (for example, Prospect Park). All inputs will be used to determine the location with the best match. For example, Big Ben returns a location in London, UK, whereas Big Ben, US returns 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 sure 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 the most accurate matches are 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, and so on. You can iterate through each GeocodeResult instance to create graphics for display in the map. Locations are returned in the GCS WGS84 (WKID: 4326).

Tip:

A Transform utility class is included with the Bing implementation of the ArcGIS API for Silverlight 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 (for example, 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:

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:

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 GCS (WKID: 4326). The ArcGIS API for Silverlight 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 WGS84 (WKID: 4326) and included in a collection that implements IEnumerable (for example, a List). The route returned passes 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 following code 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 following example, MyRoute_Complete).

// Instantiate Routing class and specify server type (production for Bing Maps keys).
Routing routing = new Routing(BingKey);
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 the 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. Locations along the route are returned in GCS WGS84 (WKID: 4326).

Tip:

A Transform utility class is included with the Bing implementation of the ArcGIS API for Silverlight 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 (for example, 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:

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 transformation 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 ItineraryItemcontained by the Itinerary property of each RouteLeg included with the results. This technique is shown in the following example. In this case, once a string containing the directions is initialized, the directions are displayed by passing that string to a TextBlock. This assumes that the TextBlock is visible somewhere within the application.

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 transformation 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 user interface (UI) with the directions.
    DirectionsTextBlock.Text = directions.ToString();
}