This is the 7th article in our "Bring the clouds together: Azure + Bing Maps" series. You can find a preview of live demonstration on http://sqlazurebingmap.cloudapp.net/. For a list of articles in the series, please refer to http://blogs.msdn.com/b/windows-azure-support/archive/2010/08/11/bring-the-clouds-together-azure-bing-maps.aspx.

Update: Now that Bing Maps AJAX SDK 7.0 is released, this post has been updated to provide code for both SDK 6.3 and SDK 7.0. Our final sample code is built for v7.0.

Introduction

Our previous posts focused on the services side. We've introduced how to design a scalable cloud database, how to work with spatial data in SQL Azure, how to access data using Entity Framework, and how to write data centric web services using WCF Data Services.

In a team environment, multiple developers can work simultaneously. So while data and service developers are working on the server side, it is perfectly valid for a client developer to start working with client applications.

This article targets client software developers who want to integrate Bing Maps to their rich HTML applications. We assume the audiences have no previous knowledge of Bing Maps SDK, but have basic knowledge of HTML 4, JavaScript, CSS, and jQuery. No HTML 5 knowledge is used.

We classify this post as an article for beginners. We'll briefly introduce how to develop with Bing Maps SDK. After complete reading this post, you will be able to display a map, invoke REST services to obtain location information, and operate pushpins. To learn more advanced topics, please refer to the interactive SDK. This post only targets HTML applications. We'll write how to integrate Bing Maps with Silverlight and Windows Phone applications in future posts.

Bing Maps SDKs

Bing Maps is a Software as a Service (SaaS) offer from Microsoft. Both consumers and developers can use it.

For consumers, Bing Maps offers two versions. A HTML version which supports basic map navigation and searching, and a Silverlight version which offers smoother browsing experience and more third party applications integration.

For developers, there several versions of Bing Maps SDK:

  • Bing Maps AJAX Control, which targets rich HTML applications.
  • Bing Maps Silverlight Control, which targets desktop Silverlight applications running on Windows, Mac, or out of browser. Those are applications that are not submitted to bing.com.
  • Bing Maps Silverlight Control for Windows Phone, which is included in the Windows Phone Developer Tools. This is a slight variation of the desktop Silverlight version that adopts a smaller touch screen.
  • Bing Maps App SDK, which is currently in beta, allows third party developers to submit their applications directly to bing.com, instead of the developer's own server or another cloud offering such as Azure. After being reviewed, consumers can use those applications just as if they're part of Bing Maps itself.
  • Bing Maps SOAP Services, which can be used in any platforms that support SOAP 1.1.
  • Bing Maps REST Services, which can be used in any platforms that support HTTP 1.1.
  • Bing Maps Spatial Data Services, which allows client applications to calculate spatial data without a custom service application.

The SOAP/REST services offer the following features:

  • Geocode/Location Service, which is used to search a place's information (such as name and address) given the latitude/longitude, or the other way round.
  • Imagery Service, which returns individual images that composes a map. This service is usually used on platforms (such as WPF, native Windows applications, and Java desktop applications) that do not have a built-in map control.
  • Route Service, which helps to calculate a route between multiple stops.
  • Search Service, which is used to perform general searches. This is a SOAP only service.

As you can see, Bing Maps can be used in almost any platforms. If you're working with HTML, Silverlight, or Windows Phone, you can take advantage of the map controls which have done almost everything for you. If you're working with other platforms, you can either embed a web browser in a client application, or invoke the SOAP/REST services. The SOAP/REST services can be used in HTML/Silverlight/Windows Phone as well, to obtain information that are not included in the map controls.

Obtain a Bing Maps developer key

Most SDKs, although not all, require a developer key to use. It is free to register a key. Just follow these steps:

  • Type an application name and a URL, choose the application type, and click Create key. You can type anything you like, including localhost.

Display a basic map

To display a map, follow these steps:

1. Create a container, which is usually a div. Give it an ID so we can reference it later.

        <div id="MainMap" style="width: 100%; height: 600px; position: absolute" />

2. If you're using SDK 6.3, there's a compatibility issue between IE9 beta and Bing Maps AJAX Control. So it is recommended to force IE9 to use IE8's standard to render the document. This issue doesn't affect the AJAX map on bing.com. It also doesn't affect Silverlight applications. If you're using SDK 7.0, IE9 beta is supported, so you don't need to fallback to IE8's standard.

    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8"> 

3. Link to the SDK JavaScript code. Currently the highest SDK version is v7.0. But you can also use v6.3 if you don't want to change existing code.

<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3"></script>   

<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

4. After the HTML document is loaded, we can intialize the map. create a new instance of the map object, set the credential (the key you registered in the previous step), and load the map.

Note the initialization code for SDK 6.3 is different from 7.0.

If you're using SDK 6.3, create a new instance of VEMap, and pass the ID of the container as a parameter. Then invoke SetCredentials to set the credential (the key you registered in the previous step). Finally invoke LoadMap to load the map. LoadMap takes an optional parameter representing the central location (latitude/longitude) of the map.

If you're using SDK 7.0 create a new instance of Microsoft.Maps.Map. The constructor itself accepts parameters representing the map's credential and center point. So no additional method invoking is needed.

In SDK 7.0, you will encounter the namespace Microsoft.Maps in a lot of places. So you may want to give it an alias:

var Bing = Microsoft.Maps;

Later you can use new Bing.Map instead of new Microsoft.Maps.Map. This is what we will use in the remaining of the post. But please do not confuse. The actual namespace is Microsoft.Maps. Bing is just a short alias we use in the sample.

Also note our application heavily uses jQuery. If you haven't worked with jQuery before, it is recommended to get started from http://docs.jquery.com/Tutorials.

var mapCredential = 'change to your key';

var map; 

$(document).ready(LoadMap);

// SDK 6.3:

function LoadMap() {

    map = new VEMap('MainMap');

    map.SetCredentials(mapCredential);

    map.LoadMap(new VELatLong(31, 121));

}

// SDK 7.0:

function LoadMap() {

    map = new Bing.Map($('#MainMap')[0],

    {

        credentials: mapCredential,

        center: new Bing.Location(31, 121)

});

}

Now a bare map will be displayed:

 

Attach event handlers

To attach event handlers to the map control, you invoke the AttachEvent method in SDK 6.3, or invoke Microsoft.Maps.Events.addHandler in SDK 7.0. Note certain event names are different for different SDKs.

// SDK 6.3:   

map.AttachEvent('onmousedown', Map_OnMouseDown);

map.AttachEvent('onmouseup', Map_OnMouseUp);

// SDK 7.0:

    Bing.Events.addHandler(map, 'mousedown', Map_OnMouseDown);

    Bing.Events.addHandler(map, 'mouseup', Map_OnMouseUp);

There're a bunch of events on the map class you can handle. Please refer to http://msdn.microsoft.com/en-us/library/bb412543.aspx (SDK 6.3) and http://msdn.microsoft.com/en-us/library/gg427609.aspx (SDK 7.0) for the complete list. But be aware, while there is an onclick event, it will be fired whenever the mouse is clicked. That is, if you hold down the mouse, pan the map, and then release the mouse, this event will also be fired. In certain cases, this may not be the behavior you want. For example, you may wish to add a pushpin only if the user clicks the map without panning around. In such cases, you need to handle onmousedown/up events directly, and write logic to figure out if the mouse has moved.

// SDK 6.3:

var mouseDownLocation;

function Map_OnMouseDown(e) {

    mouseDownLocation = new VEPixel(e.mapX, e.mapY);

}

 

function Map_OnMouseUp(e) {

    var pixel = new VEPixel(e.mapX, e.mapY);

    // Only add a pushpin if the user is not panning the map.

    if (mouseDownLocation != null && mouseDownLocation.x == pixel.x && mouseDownLocation.y == pixel.y) {

        // Do something…

    }

}

// SDK 7.0:

function Map_OnMouseDown(e) {

    mouseDownLocation = new Bing.Point(e.pageX, e.pageY);

}

 

function Map_OnMouseUp(e) {

    var pixel = new Bing.Point(e.pageX, e.pageY);

    // Only add a pushpin if the user is not panning the map.

    if (mouseDownLocation != null && mouseDownLocation.x == pixel.x && mouseDownLocation.y == pixel.y) {

        // Do something…

    }

}

Invoke Location Service

When the user clicks the map, we will add a pushpin on the clicked location. This operation doesn't require a round trip to the service, because you can use the PixelToLatLong (SDK 6.3) or tryPixelToLocation (SDK 7.0) method to obtain the clicked point's location, which is enough to create a pushpin. However, our application requires more than the location's latitude and longitude. We also need at least the name (place) of the clicked location. This must be done with a service call.

Since we're working with an HTML application, the simplest solution is to use the REST services, which supports both XML and JSON formats. We'll use JSON here since we're using JavaScript and jQuery. To obtain detailed information of a location given the latitude and longitude, we can use the Geocode/Location Service. There're several usages of this service. If you know the latitude/longitude, the URI of the service is:

http://dev.virtualearth.net/REST/v1/Locations/[latitude],[longitude]?key=[your key]

You can also specify additional query strings such as o (format) and jsonp (to workaround the cross domain issue).

// SDK 6.3:       

        var latLong = map.PixelToLatLong(pixel);

// SDK 7.0:

        var latLong = map.tryPixelToLocation(pixel, Bing.PixelReference.page);       

// Common:

// Invoke the Location REST service to obtain information of the clicked place.

        $.ajax(

               {

                   url: 'http://dev.virtualearth.net/REST/v1/Locations/' + latLong.Latitude + ',' + latLong.Longitude + '?o=json&jsonp=LocationCallback&key=' + mapCredential,

                   dataType: 'jsonp',

                   jsonp: 'LocationCallback',

                   success: LocationCallback

               });

The response looks like the following:

LocationCallback(

{

    "authenticationResultCode":"ValidCredentials",

    "brandLogoUri":"http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png",

    "copyright":"Copyright © 2010 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.",

    "resourceSets":[

    {

        "estimatedTotal":1,

        "resources":[

        {

            "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",                        "bbox":[35.885187361790223,139.56395563345109,35.892912796931576,139.57666936654891],

            "name":"Saitama",

            "point":

            {

                "type":"Point",

                "coordinates":[35.8890500793609,139.5703125]

            },

            "address":

            {

                "adminDistrict":"Saitama",

                "countryRegion":"Japan",

                "formattedAddress":"Saitama"

            },

            "confidence":"Medium",

            "entityType":"AdminDivision1"

        }]

    }],

    "statusCode":200,

    "statusDescription":"OK",

    "traceId":"c1d54e51791b465ca0ebc34e04d34618|BAYM001224|02.00.147.700|BY2MSNVM001066, BY2MSNVM001087"

})

 

As you can see, the response may contain multiple results (resources) that are located near the provided latitude/longitude. Our application only cares about the first resource. Each resource contains multiple information, such as the name of the location, and the exact latitude/longitude. Those are the information we need for our application.

function LocationCallback(result) {

    if (result.resourceSets.length > 0) {

        var resourceSet = result.resourceSets[0];

        if (resourceSet.resources.length > 0) {

            var resource = resourceSet.resources[0];

 

            // Add a pushpin.

            // SDK 6.3:

            var point = new VEShape(VEShapeType.Pushpin, new VELatLong(resource.point.coordinates[0], resource.point.coordinates[1]));

            point.SetTitle(resource.name);

            map.AddShape(point);

            // SDK 7.0:

            var pushpin = new Bing.Pushpin(new Bing.Location(resource.point.coordinates[0], resource.point.coordinates[1]));

            map.entities.push(pushpin);

        }

    }

}

For more information about the response format, please refer to http://msdn.microsoft.com/en-us/library/ff701710.aspx.

Create a shape and add it to the map

As demonstrated in the above code, to add a pushpin to the map, first create a VEShape (SDK 6.3) or Microsoft.Maps.Pushpin (SDK 7.0), and then invoke VEMap.AddShape or Microsoft.Maps.Map.entities.push. There're 3 kinds of shapes: pushpin (a single point), polyline, and polygon. Our application only uses pushpin. But it is also very easy to add a polyline and a polygon. Simply provide multiple points. And in SDK 7.0, choose a different class. Take the sample from http://msdn.microsoft.com/en-us/library/bb877865.aspx:

var points = new Array(
new VELatLong(45.01188,-111.06687, 0, VEAltitudeMode. RelativeToGround),
new VELatLong(45.01534,-104.06324, 0, VEAltitudeMode. RelativeToGround),
new VELatLong(41.01929,-104.06, 0, VEAltitudeMode. RelativeToGround),
new VELatLong(41.003,-111.05878, 0, VEAltitudeMode. RelativeToGround)
);
var myPolygon = new VEShape(VEShapeType.Polygon, points);
var myPolygon = map.AddShape(myPolygon);

 

In SDK 6.3, you can also set a title for the shape by invoking the SetTitle method, which will be displayed when the user hovers mouse over the shape.

In SDK 7.0, this behavior is not provided out of box. So we have to it ourself. Since JavaScript is a dynamic language, we can add properties dynamically to an object. For example, you can write pushpin.title = resource.name;, even if the Pushpin class doesn't have a title property defined.

To display a popup, you're required to create the html elements, and write some code to display the elements. Here we provide a simple implementation. First we put a div inside the map:

        <div id="MainMap" style="width: 100%; height: 600px; position: absolute">

            <div id="PushpinPopup">

                <img src="Images/Cloud Dialog.png" alt="Pushpin Popup" style="position:absolute"/>

                <div id="PushpinText"></div>

            </div>

        </div>

Then set the styles for the elements. Note the popup's z-index is set to a very large value, so the map images won't cover it.

#PushpinPopup

{

    z-index: 10000;

    position: absolute;

    opacity: 0;

    width: 200px;

    text-align: center;

}

 

#PushpinText

{

    position: relative;

    margin-top: 20px;

}

Now we can handle the pushpin's mouseover/out events, and display/hide the popup:

    Bing.Events.addHandler(pushpin, 'mouseover', Pushpin_MouseOver);

    Bing.Events.addHandler(pushpin, 'mouseout', Pushpin_MouseOut);

 

function Pushpin_MouseOver(e) {

    $('#PushpinText').text(e.target.title);

    var pushpinPopup = $('#PushpinPopup');   

    var pixel = map.tryLocationToPixel(e.target.getLocation(), Bing.PixelReference.control);

    pushpinPopup.css('left', pixel.x - 100);

    pushpinPopup.css('top', pixel.y);

    pushpinPopup.animate({ opacity: 1 });

}

function Pushpin_MouseOut() {

    $('#PushpinPopup').animate({ opacity: 0 })

}


Conclusion

This post provided an introduction to Bing Maps AJAX Controls and Bing Maps REST Services. As you can see, it is very easy to use Bing Maps in your own application. Most of the code described in this post uses standard JavaScript and jQuery. If you wish to learn more, please refer to the documentation at http://msdn.microsoft.com/en-us/library/dd877180.aspx, and the interaction SDK at http://www.microsoft.com/maps/isdk/ajax/.

The next post will introduce jQuery Templates, a joint investment of Microsoft and jQuery. You'll learn how it helps you to display structured data (the travel stop list).