October 2010

Volume 25 Number 10

Bing Map Apps - Building a Real-Time Transit Application Using the Bing Map App SDK

By Luan Nguyen | October 2010

During the Tech•Ed 2010 conference in June, Microsoft announced the availability of the free Bing Map App SDK, which enables developers to write map-centric applications on top of the Bing Maps explore site located at bing.com/maps/explore/.

This presents ample opportunities for organizations, businesses or hobbyists to create their own mapping experiences within Bing Maps. Businesses can write apps to advertise their products or to complement their online services. For example, bestparking.com developed a “Parking finder” app, which helps you find all parking facilities in your city using their database.

To see what a map app looks like, go to the Bing Maps explore site and click on the MAP APPS button located near the bottom left corner of the page. The Map apps gallery will open, showing the available apps. There are already many apps featured in the gallery, and it continues to grow.

In this article, I’ll describe what it takes to write a map app using the Bing Map App SDK. I’ll walk you through the development of a sample app that shows real-time bus arrival information around King County in Washington state. This app is inspired by the popular One Bus Away app.

The Final Product

Before I delve into code, let’s take a look at what the final map app will look like. When activated, if the map is zoomed into the King County area, my map app will show in the left panel the list of all bus stops within the map viewport (see Figure 1).

image: The OBA App Shows Bus Information on the Left Panel and Pushpins on the Map

Figure 1 The OBA App Shows Bus Information on the Left Panel and Pushpins on the Map

Each bus stop has information about all buses serving that stop. Clicking on the “Arrival times” hyperlink for each stop will bring up the second panel, which displays upcoming arrival times (see Figure 2).

image: Bus Arrival Times for a Particular Bus Stop

Figure 2 Bus Arrival Times for a Particular Bus Stop

The app also places a visually appealing pushpin (marked with the letter B) at every bus stop on the map. You can interact with the pushpins to activate a pop-up, from which you can invoke common commands for a particular bus stop, such as getting driving or walking directions to or from it (see Figure 3).  

image: Clicking on a Pushpin Shows a Pop-Up UI with Extra Information

Figure 3 Clicking on a Pushpin Shows a Pop-Up UI with Extra Information

Download the Bing Map App SDK

Before starting to write your first map app, you need to install the Bing Map App SDK at connect.microsoft.com/bingmapapps. The SDK provides the reference assemblies, one sample project and offline documentation. In addition, because the SDK is based on Silverlight 4, you also need to install the Silverlight 4 Tools for Visual Studio 2010, available at silverlight.net/getstarted/.

Map App Basics

The Bing Maps explore site was built with extensibility as one of the top priorities. The Bing Maps team wanted to enable developers around the world to easily add functionality that they would find useful and that would be potentially useful to others. In that sense, a map app is a concrete feature that makes use of the building block services provided by the core of Bing Maps. If you look at the default Bing Maps site, the driving directions, business search and location search features are all built from the same building blocks.

With that goal in mind, the Bing Maps team decided to build an extensible framework to support the concept of map apps. When written on top of the framework, map apps take advantage of a dependency injection-style approach to provide access to functionality, similar to that found in the Managed Extensibility Framework (MEF).

At the core of the framework is the concept of plug-ins. A plug-in is a unit of extensibility that allows developers to add features in a maintainable, decoupled way. It can be thought of as the equivalent of a composable part in MEF terminology. Through attributes, a plug-in declares the imports it needs as well as exports it wants to share. At run time, the framework will resolve dependencies among plug-ins and match the imports with the exports of the same contract.

To write a map app, you create a Silverlight 4 class library that contains exactly one subclass of the base Plugin class. In order to do anything useful, your plug-in will likely import a number of built-in contracts that allow you to access various core services. The SDK documentation has a section listing all of the built-in contracts exported by the framework.

To reference the Plugin class and all built-in contract types, your project will need to reference the provided assemblies from the SDK. Figure 4 briefly describes these four assemblies.

Figure 4 Reference Assemblies for a Map App Project

Assembly Name Description
Microsoft.Maps.Plugins.dll Contains the base Plugin class and related Import/Export attribute classes.
Microsoft.Maps.Core.dll Contains all the contract types that Bing Maps provides.
Microsoft.Maps.MapControl.Types.dll Contains the types required for working with the map control.
Microsoft.Maps.Extended.dll Contains the types to interact with the StreetSide map mode.

Access the One Bus Away API

To obtain real-time bus arrival information, I’ll be making use of the publicly available One Bus Away REST API (simply referred to as Oba API for the rest of this article). You’ll find details about the Oba API at https://msdn.microsoft.com/en-us/magazine/dd263100.aspx. (Note that the API is currently free for noncommercial use, but you’re required to register for an application key before you can access it. In the downloadable code for this article, I’ve removed the key assigned to me, so if you want to compile and try the app yourself, you need to replace it with your own key.)

When a Silverlight application tries to access a Web service on a different domain, the Silverlight runtime mandates that the service must explicitly opt in to allow cross-domain access. A service indicates its consent by placing either a clientaccesspolicy.xml or crossdomain.xml file at the root of the domain where the service is hosted. More details regarding the schemas of these two files are documented at msdn.microsoft.com/library/cc197955%28VS.95%29. Luckily, the Oba API provides the crossdomain.xml file, which allows my map app to call it in Silverlight code.

In the downloadable solution, you’ll see two library projects, ObaApp and ObaLib. ObaApp is the main project, which contains the map app plug-in, and it references ObaLib. ObaLib is another class library project that encapsulates all helper classes to communicate with the Oba API. I made it a separate library so I can easily reuse it in different projects if I need to. I won’t go into the details of every class in this library, but you’re encouraged to examine the source code to learn more about the classes in there.

The most important class to know is the ObaSearchHelper class, which provides convenient methods and events to make queries to the Oba API:

public sealed class ObaSearchHelper
{
  public void SearchArrivalTimesForStop(string stopId);
  public event EventHandler<BusTripsEventArgs>
    SearchArrivalTimesForStopCompleted;
  public void SearchStopsByLocation(double latitude, double longitude, 
    double radius, int maxResultCount);
  public event EventHandler<BusStopsEventArgs> 
    SearchStopsByLocationCompleted;
  // more method/event pairs
  // ...
}

It’s easy to spot the pattern used in this class. For every REST endpoint of the Oba API, there’s one method to trigger the search and one corresponding event to signal the completion of that search. Subscribers of the events can obtain the results from event argument objects.

With this handy class ready, let’s dissect the main classes in the ObaApp project.

Define the Map Entity

The first thing you need to do when writing a map app is to define your map entity. Here’s the definition of entity from the SDK documentation: “An Entity is a geo-associated item on the map. A map entity can be a point, a polyline, a polygon or an image overlay.” The simple rule of thumb is that if you want to place something on the map, you need an instance of Entity to represent it. Usually, you want to create a subclass of Entity to add extra properties and custom logic for your entities. In my app, because I want to show the locations of bus stops, I wrote a BusStopEntity class to represent a bus stop. Figure 5 shows the BusStopEntity class.

Figure 5 The BusStopEntity Class

public class BusStopEntity : Entity
{
  private readonly DependencyProperty NamePropertyKey =
    Entity.RetrieveProperty(“Name”, typeof(string));
  private BusStop _busStop;
  // Short name of this bus stop
  public string Name
  {
    get { return _busStop.Name; }
  }
  // Unique id of this bus stop
  public string StopId
  {
    get { return _busStop.Id; }
  }
  public BusStopEntity(BusStop busStop, 
    PushpinFactoryContract pushpinFactory)
  {
    _busStop = busStop;
    // Set the location of this Entity
    Location location = 
      new Location(_busStop.Latitude, _busStop.Longitude);
    this.Primitive = pushpinFactory.CreateStandardPushpin(location, “B”);
    // Set the name of this Entity
    this.SetValue(NamePropertyKey, _busStop.Name);
  }
}

My BusStopEntity class exposes two properties, Name and StopId, which I’ll later use to data-bind to UI controls. These values come from the underlying BusStop instance. Defined in the ObaLib project, the BusStop class encapsulates data of a bus stop, which it gets from the Oba API.

In the constructor, I also set the Primitive and the Name properties. The Primitive property represents the type (for example, point, polygon or polyline) and location of my entity. I set the location to the longitude and latitude values of the BusStop instance. In addition, setting the Primitive property to the returned value of PushpinFactoryContract.CreateStandardPushpin gives my entity the look and feel of the standard Bing Maps pushpins. The PushpinFactoryContract type provides handy methods for creating common pushpin UI elements. You don’t have to use it if you want to create your own custom pushpin shapes. Here I just simply put a letter B (for bus) inside the pushpin, but you can use an image or any UIElement.

Although I define the Name property in my subclass, it won’t be recognized by other types in the SDK—those types simply have no knowledge of my property. Therefore, I also set the same value to the Name dependency property, which I retrieve via the call Entity.RetrieveProperty(“Name”, typeof(string)). By doing this, I enable other features to retrieve the name of a bus stop entity.

Write the Main Plug-in

As explained earlier, you need a Plugin-derived class to represent your map app. Figure 6 shows mine, which is aptly named ObaPlugin. My plug-in imports a total of six contracts. Notice that all contracts provided by Bing Maps follow the naming convention of Microsoft/*. The SDK documentation provides detailed descriptions of all contracts that can be imported, shown in Figure 7.

Figure 6 Imports Declared in ObaPlugin Class

public class ObaPlugin : Plugin
{
  #region Imports
  [ImportSingle(“Microsoft/LayerManagerContract”, ImportLoadPolicy.Synchronous)]
  public LayerManagerContract LayerManager { get; set; }
  [ImportSingle(“Microsoft/PopupContract”, ImportLoadPolicy.Synchronous)]
  public PopupContract PopupContract { get; set; }
  [ImportSingle(“Microsoft/ConfigurationContract”, 
    ImportLoadPolicy.Synchronous)]
  public ConfigurationContract ConfigurationContract { get; set; }
  [ImportSingle(“Microsoft/MapContract”, ImportLoadPolicy.Synchronous)]
  public MapContract Map { get; set; }
  [ImportSingle(“Microsoft/PushpinFactoryContract”, 
    ImportLoadPolicy.Synchronous)]
  public PushpinFactoryContract PushpinFactory { get; set; }
  [ImportSingle(“Microsoft/ContributorHelperContract”, 
    ImportLoadPolicy.Synchronous)]
  public ContributorHelperContract ContributorHelper { get; set; }
  #endregion

Figure 7 Contracts Imported by the ObaPlugin

Contract Type Description
LayerManagerContract Allows a plug-in to add or remove map layers.
PopupContract Allows a plug-in to show the pop-up 
UI when user hovers or clicks on the 
entities/pusphins.
ConfigurationContract Allows a plug-in to dynamically load configuration values from the configuration file at run time.
MapContract Allows a plug-in to control the map.
PushpinFactoryContract Allows a plug-in to render standard pushpins for its entities.
ContributorHelperContract Allows a plug-in to create asynchronous or demand-load contributors. (I’ll use this helper to add “Driving directions” and “StreetSide” contributor links to my pop-up later.)

After declaring the imports, I override the virtual Initialize method:

public override void Initialize()
{
  // Obtain the application key from configuration file
  IDictionary<string, object> configs = 
    ConfigurationContract.GetDictionary(this.Token);
  string appKey = (string)configs[“ApplicationKey”];
  _searchHelper = new ObaSearchHelper(appKey);
  _searchHelper.SearchStopsByLocationCompleted += 
    OnSearchBusStopsCompleted;
  _searchHelper.SearchArrivalTimesForStopCompleted += 
    OnSearchArrivalTimesComplete;
  // Update the bus stop every time map view changes
  Map.TargetViewChangedThrottled += (s, e) => RefreshBusStops();
}

This method is called exactly once after the plug-in instance is constructed and all the declared imports are satisfied. This is the perfect place to do any initialization work that depends on the imported contracts. If your plug-in has any exports, you also have to make sure all the exported properties are fully instantiated by the time Initialize returns.

I first obtain the application key from the configuration file via the imported ConfigurationContract instance and pass it to the ObaSearchHelper constructor. By putting the application key in the configuration file, I can easily change it at any time without recompiling my project.

The second thing I do is hook up the event TargetViewChangedThrottled from the MapContract instance. This event is raised every time the map’s view is about to change, either programmatically or through user interaction. As the name suggests, the event is throttled internally so that it doesn’t fire too many times within a short duration. Therefore, it’s a perfect event to consider if you want to keep your entities in sync with the map view. In my case, I call the RefreshBusStops method to refresh the list of bus stops. Here’s the definition of RefreshBusStops:

internal void RefreshBusStops()
{
  if (Map.ZoomLevel >= 14)
  {
    // Search within the radius of 1km, maximum 150 results
    _searchHelper.SearchStopsByLocation(
      Map.Center.Latitude, Map.Center.Longitude, radius: 1000, 150);
  }
  else
  {
    // Clear all bus stops
    ClearBusStops();
  }
}

Here, I check that the current map zoom level is at least 14, then I issue a call to the SearchStopsByLocation method, which will return the list of all bus stops within a specified radius around the map center. Otherwise, if the zoom level is less than 14, meaning the map is not zoomed in close enough to the city level, I clear out all bus stops.

When the SearchStopsByLocation method completes (asynchronously), it raises the SearchStopsByLocationCompleted event, shown in Figure 8, which I subscribed to earlier in the Initialize method.

Figure 8 Handler for the SearchBusStopsCompleted Event

private int _searchCount;
private Dictionary<BusStop, int> _busStopCache = 
  new Dictionary<BusStop, int>();
private ObaLayer _layer;
private void OnSearchBusStopsCompleted(object sender, BusStopsEventArgs e)
{
  _searchCount++;
  // Contains new bus stops not present in the current view 
  Collection<BusStopEntity> addedEntities = 
    new Collection<BusStopEntity>();
  foreach (BusStop stop in e.BusStops)
  {
    // If this bus stop is not in the cache, it is a new one
    if (!_busStopCache.ContainsKey(stop))
    {
      addedEntities.Add(new BusStopEntity(stop, PushpinFactory));
    }
    // Marks this bus stop as being in the current search
    _busStopCache[stop] = _searchCount;
  }
  // Contains the old bus stops 
  // that should be removed from the current view
  Collection<BusStopEntity> removedEntities = 
    new Collection<BusStopEntity>();
  foreach (BusStopEntity bse in _layer.Entities)
  {
    // This bus stop belongs to the previous search, 
    // add it to removed list
    if (_busStopCache[bse.BusStop] < _searchCount)
    {
      removedEntities.Add(bse);
      _busStopCache.Remove(bse.BusStop);
    }
  }
// Tells the layer to add new in-view entities 
// and remove out-of-view ones 
      _layer.RefreshEntities(addedEntities, removedEntities);
}

Note the object of type ObaLayer, which I’ll explain in the next section. For now, suffice it to say that this object is responsible for managing the left panel’s content and entities on the map.

Notice that I use a Dictionary<BusStop, int> object to help me keep track of the current list of displayed bus stops. With the help of this dictionary, every time I get a brand-new set of bus stops from the service call, I can quickly determine the new stops that aren’t already shown, as well as the old stops that are now out of view due to the map view change.

You may wonder why I don’t just clear out all current bus stops and show the new set of bus stops altogether. Well, the reason is performance. If I did so, I’d force all the pushpin controls to be re-created even if many of them were at the same location in both the old and new map views. Even worse, users would see a short flash when the pushpins were removed and quickly added back. My approach eliminates both of these shortcomings.

Show Content and Pushpins with a Layer

The Plugin class represents your map app, but if you want to show UI representations of it, you need to create layers. A layer is an abstract concept that allows you to place entities on the map as pushpins (or polylines, or polygons) and to show custom UI content on the left panel. Optionally, it can also place an arbitrary UI overlay on top of the map, but I don’t need that for my app. Most apps have only one layer.

If you’ve used Photoshop, you’ll find the concept very similar to Photoshop layers. The History button (located at the bottom left corner of the page) shows the list of all currently loaded layers. You can choose to show or hide any layer, or you can set a layer to become active. When a layer becomes active, its Panel UI element is shown in the left panel and all of its entities are brought forward in the z-index stack.

In code, a layer is a subclass of the abstract Layer class. As shown in Figure 9, ObaPlugin creates and manages an instance of the ObaLayer class, which derives from Layer.

Figure 9 baLayer Class

internal class ObaLayer : Layer
{
  private ObaPlugin _parent;
  private BusStopsPanel _resultPanel;
  private ObservableCollection<BusStopEntity> _busStopEntities;
  public ObaLayer(ObaPlug-in parent) : base(parent.Token)
  {
    _parent = parent;
    // Contains the set of active bus stop entities 
    // for data binding purpose
    _busStopEntities = new ObservableCollection<BusStopEntity>();
    _resultPanel = new BusStopsPanel(parent, this);
    _resultPanel.DataContext = _busStopEntities;
    this.Title = “Bus stops”;
    this.Panel = _resultPanel;
  }
  ...
}

In the constructor of ObaLayer, I set the title of my layer, which will show at the top of the left panel. I also set the Panel property to an instance of the BusStopsPanel user control, which will occupy the entire left panel region if my layer becomes active. The user control’s DataContext property is set to an instance of ObservableCollection.

So how does the layer get displayed? That’s done by ObaPlugin as shown in Figure 10.

Figure 10 ShowResultLayer Method Shows the ObaLayer Instance to the User

public override void Activate(IDictionary<string, string> activationParameters)
{
  ShowResultLayer();
  RefreshBusStops();
}
private void ShowResultLayer()
{
  if (_layer == null)
  {
    _layer = new ObaLayer(this);
  }
  if (LayerManager.ContainsLayer(_layer))
  {
    LayerManager.BringToFront(_layer);
  }
  else
  {
    LayerManager.AddLayer(_layer);
  }
}

The override Activate method is called every time my map app is activated through the Map apps gallery. To show my layer, I refer to the LayerManagerContract type I imported earlier. The LayerManagerContract class defines methods to work with layers. If my layer is already added, I set it to active by calling the BringToFront method. Attempting to add the same layer twice results in an exception.

In Figure 8, I called the ObaLayer.RefreshEntities method to update the bus stops. Figure 11 shows its definition.

Figure 11 The ObaLayer.RefreshEntities Method Definition

public void RefreshEntities(
  ICollection<BusStopEntity> addedEntities,
  ICollection<BusStopEntity> removedEntities)
{
  foreach (BusStopEntity entity in removedEntities)
  {
    // Remove this pushpin from the map
    this.Entities.Remove(entity);
    // Remove this bus stop entry from the panel
    _busStopEntities.Remove(entity);
  }
  foreach (BusStopEntity entity in addedEntities)
  {
    // Add this pushpin to the map
    this.Entities.Add(entity);
    // Add this bus stop entry to the panel
    _busStopEntities.Add(entity);
    // Register this entity to have popup behavior
    _parent.PopupContract.Register(entity, OnPopupStateChangeHandler);
  }
}

To add or remove an entity on the map, I use the Layer.Entities collection property. And for every new BusStopEntity, I call the PopupContract.Register method to register for the pop-up UI on my bus stop pusphin. The Register method accepts the entity and a callback method that gets invoked whenever the pop-up control changes state on the entity (see Figure 12).

Figure 12 Changing the State of a Pop-Up Control

private void OnPopupStateChangeHandler(PopupStateChangeContext context)
{
  if (context.State == PopupState.Closed)
  {
    return;
  }
  BusStopEntity entity = (BusStopEntity)context.Entity;
  context.Title = entity.Name;
  context.Content = “Bus numbers: “ + entity.BusRoutesAsString;
  // Only shows contributors link in the normal state of popup
  if (context.State == Pop-upState.Normal)
  {
    // Add Arrival times contributor
    context.Contributors.Add(new BusStopContributor(_parent, entity));
    Dictionary<string, object> parameter = new Dictionary<string, object>
    {
      {“Entity”, entity}
    };
    var contributorHelper = _parent.ContributorHelper;
    // Add Directions contributor
    context.Contributors.Add(contributorHelper.
      CreateDemandLoadContributor(
      “Microsoft/DirectionsContributorFactoryContract”, 
      parameter, “Directions”));
    // Add Streetside contributor
    context.Contributors.Add(contributorHelper.CreateAsyncContributor(
      “Microsoft/StreetsideContributorFactoryContract”, parameter));
  }
}

Inside the pop-up callback, the method argument of PopupStateChangeContext type gives me access to the current state of the pop-up and the entity currently under pop-up. Based on those, I set the pop-up title with the bus stop name and the pop-up content with the BusStopEntity.BusRoutesAsString property, which returns a comma-separated list of bus routes serving this particular bus stop.

If the pop-up is in Normal state, I also add three contributor links to the pop-up via the Contributors collection property—the “Arrival times” contributor shows the arrival times for this bus stop, the Directions contributor invokes the driving directions feature and the Streetside contributor switches to street-level map mode.

A contributor is represented by a hyperlink at the bottom of the pop-up control. It allows map apps to invoke a certain core feature of Bing Maps. To generate contributors, you call one of the two methods of the ContributorHelperContract type: CreateAsyncContributor or CreateDemandLoadContributor. Both methods return a proxy contributor synchronously and defer the loading of the real contributor instance. The only difference is that the CreateAsyncContributor loads the real contributor as soon as it returns, whereas CreateDemandLoadContributor only does so when the proxy contributor link is invoked the first time.

BusStopsPanel UserControl

BusStopsPanel is a UserControl, which is responsible for showing the list of in-view bus stops in the left panel (as shown in Figure 1). It contains an ItemsControl instance with the ItemTemplate property set to a custom DataTemplate (see Figure 13). Notice that I turn on UI virtualization mode for my ItemsControl by setting the ItemsPanel property to use a VirtualizingStackPanel. Generally, it’s a good practice to apply UI virtualization to ItemsControl if your app may load hundreds of items into it.

Figure 13 The BusStopsPanel UserControl

<UserControl.Resources>
  <DataTemplate x:Key=”BusStopTemplate”>
    <Border Margin=”2,0,2,12”
      <StackPanel>
        <TextBlock Text=”{Binding Name}” FontSize=”14” FontWeight=”Bold”  
          TextWrapping=”Wrap” />
        <TextBlock Text=”{Binding BusRoutesAsString, StringFormat=’Bus numbers:
          {0}’}” FontSize=”12” TextWrapping=”Wrap” />
          <HyperlinkButton Content=”Arrival times” Click=”OnBusStopClick” 
            Style=”{StaticResource App.P2.Hyperlink}”         
              HorizontalAlignment=”Left” />
      </StackPanel>
    </Border>
  </DataTemplate>
  <ControlTemplate x:Key=”ScrollableItemsControl”    
    TargetType=”ItemsControl”>
    <ScrollViewer Style=”{StaticResource App.ScrollViewer}”  
      VerticalScrollBarVisibility=”Auto”>
      <ItemsPresenter />
    </ScrollViewer>
  </ControlTemplate>
</UserControl.Resources>
    
<ItemsControl 
  ItemsSource=”{Binding}”
  VirtualizingStackPanel.VirtualizationMode=”Recycling”
  ItemTemplate=”{StaticResource BusStopTemplate}” 
  Template=”{StaticResource ScrollableItemsControl}”>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ItemsControl>

Inside the bus stop DataTemplate I added a HyperlinkButton control, which, when clicked, will trigger the search for the arrival times of the corresponding bus stop:

private void OnBusStopClick(object sender, RoutedEventArgs e)
{
  FrameworkElement element = (FrameworkElement)sender;
  BusStopEntity entity = (BusStopEntity)element.DataContext;
  _plugin.SearchArrivalTimes(entity);
}

Notice that its Style property is set to an object from the StaticResource collection, with the key as “App.P2.Hyperlink.” Bing Maps provides a default set of UI resources, such as Styles and ControlTemplates of common controls, as well as standard Colors and Brushes used by Bing Maps itself. Map app authors are encouraged to apply these resources to their UI elements so they have the same look and feel as native UI elements. Refer to the documentation for all the resources provided by Bing Maps.

SearchArrivalTimes is an internal method of the ObaPlugin class. It calls the ObaSearchHelper.SearchArrivalTimesForStop method to retrieve arrival times for the specified bus stop:

internal void SearchArrivalTimes(BusStopEntity _entity)
{
  _searchHelper.SearchArrivalTimesForStop(_entity.StopId, _entity);
}

When the search completes, the plug-in will instruct ObaLayer to show the arrival times in the left panel. ObaLayer does so by dynamically changing its Title and Panel properties.

Testing and Debugging the Map App

When you’re done coding, you’ll want to test your app. The Bing Maps site has a developer mode that’s enabled by appending a “developer=1” query parameter to the URL, like this: https://www.bing.com/maps/explore/?developer=1. When in developer mode, you test your map app with the “Map app test tool,” which can be activated via the same Map apps gallery. The Map app test tool allows you to select the plug-in assemblies from your local hard drive. It then loads your plug-in into the site just as it does with all native plug-ins. To debug your code, attach VS.NET to your browser while your app is loaded. Make sure you set the debug code type to Silverlight.

Submitting Your Map App

Finally, when you’re satisfied with your code, you can submit your app to be published officially onto the Bing Maps site. You can submit a new app and view the status of previous submissions at the Bing Maps Account Center (bingmapsportal.com). The SDK documentation has detailed instructions on submitting your app, and it lists the requirements your apps must meet in order to be approved.

Call to Action

So there you have it: a full-fledged, real-time transit application that didn’t require a lot of code. The SDK supplies the building blocks that make writing map-centric applications an enjoyable and rewarding experience. I encourage you to download the SDK today, learn it, and start writing your own map apps.


Luan Nguyen  worked as a developer on the Bing Maps team (formerly known as Microsoft Virtual Earth) for almost four years. He was a member of the Shell feature team, which was responsible for the framework shell of the Bing Maps site and the Bing Map App SDK. He has recently switched to the ASP.NET team.

Thanks to the following technical experts for reviewing this article: Alan Paulin, Chris Pendleton, Dan Polivy and Greg Schechter