Bing Maps by Microsoft provide a collection of Application Programming Interfaces (APIs) that adds mapping capabilities to location-aware applications. The APIs, open to the public since their first beta release in 2005, can query for location or business by address or coordinates. They enable searching for routes, including traffic information; searching for geometrical shapes of geographical entities such as countries, regions, and other smaller administrative divisions. Consumers of Bing Maps APIs can geocode address, or reverse geocode coordinates via automated jobs.
All the APIs can be used with JavaScript, but server-side languages such as .Net, PHP, Ruby, and so on can also be used. The supported browsers include Internet Explorer Version 7 onwards, Firefox Version 3.6 onwards, Safari Version 5 onwards, Opera, Google Chrome, iPhone, Android, and Blackberry mobile browsers are also supported.
To access the APIs, a Bing Maps key is needed; it can be obtained at the Accounts Center available at https://www.bingmapsportal.com. Bing Maps offer a few access options, including a basic key for non-profit, educational purposes, with limited usage. For production scenarios, Bing Maps offer an enterprise key.
The AJAX Control is a JavaScript library that adds a fully functional map to a web page. The current version, V7, released in 2010, was written with a modular approach in mind, which reduced the core part of the library to around 35 KB. Additional modules, such as the directions module, or the ones we will write in this chapter, can be loaded dynamically on request.
In this chapter, we are going to place a map on our webpage, and customize its look. First, let's create a simple HTML5 index.html
file, as follows:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>LBM | Chapter 1: Introduction to Bing Maps</title>
</head>
<body>
</body>
</html>
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
Microsoft recommends using UTF-8
encoding on the page. To load the control, we add the script
tag to the header,
as shown in the following code snippet:
<script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
Now, let's add the div
element that will contain the map in the body,
as shown in the following code snippet:
<div id="lbm-map"></div>
We need to style our div with height and width, so let's add an external stylesheet to our page, which now should look as shown in the following code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>LBM | Chapter 1: Introduction to Bing Maps</title>
<link href="app.css" rel="stylesheet" />
<script src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
</head>
<body>
<div id="lbm-map"></div>
</body>
</html>
The width and height of the map container will be used by the AJAX Control to resize the map. The container must also be styled with relative or absolute position. So, let's code them in our stylesheet file, as follows:
#lbm-map {
position: relative;
width: 100%;
margin: 20px auto;
border: 1px solid #888;
}
We have used Learning Bing Maps (LBM) to prefix our styles.
Now, let's write the function that instantiates the map on page load in a app.js
JavaScript file as shown in the following code:
window.onload = function() { var container = document.getElementById('lbm-map'), resize = function () { container.style.height = window.innerHeight + 'px'; }; resize(); var map = new Microsoft.Maps.Map(container, { credentials: '[YOUR BING MAPS KEY]', center: new Microsoft.Maps.Location(39.554883, -99.052734), mapTypeId: Microsoft.Maps.MapTypeId.road, zoom: 4 }); window.onresize = function() { resize() }; };
First, we get a reference to the map container and store it in a container
variable for later use. Since we want our map to cover the whole browser window, we give our container the same height as the window (we already specified the width value as 100 percent).
The main function (we will also refer to them as
classes, even though this term is not supported as such in JavaScript) of the AJAX Control is named Microsoft.Maps.Map
, and accepts two arguments—the first argument is a reference to the DOM element of the map container and the second argument is a Microsoft.Maps.MapOptions
object—used to customize the map.
One of the important properties of the
MapOptions
object is credentials
, and it holds the Bing Maps key. Other properties control the view of the map, such as the zoom level, the background color, the default controls; or the map's behavior, such as panning, user clicks or touches, keyboard input, and so on (the full reference of MapOptions
can be found at http://msdn.microsoft.com/en-us/library/gg427603.aspx). Please note that Microsoft.Maps
is the namespace that groups all the JavaScript classes of the control.
Going back to our code, at the end of it, we make sure the map is resized to fill the whole window, even when the user resizes the browser window. Note how the map follows the dimensions of its container.
We need to add a reference to this file on our index.html
page, and we place the reference just before the closing </body>
tag, so that the file is loaded after all the other assets of the page have been downloaded by the browser.
<script src="app.js"></script>
If we open the index.html
file on the browser, we should get the map as shown in the following figure:

The map is centered somewhere in Kansas, United States. To do this, we set the center
option to a Microsoft.Maps.Location
instance, which accepts two parameters: latitude and longitude.
new Microsoft.Maps.Location(39.554883, -99.052734)
The zoom
option can take any value between 1 and 20, with 1 zooming out far and 20 zooming in close. The mapTypeId
property is a value of
Microsoft.Maps.MapTypeId
, which includes aerial, birdseye, and so on. (The list can be found at http://msdn.microsoft.com/en-us/library/gg427625.aspx).
Now, let's remove the map's dashboard, the controls at the top-right corner of the map, and replace them with our own. To achieve the former, we add two more options to our map instantiation function, as shown in the following code snippet:
var map = new Microsoft.Maps.Map(document.getElementById('lbm-map'), { credentials: '[YOUR BING MAPS KEY]', center: new Microsoft.Maps.Location(39.554883, -99.052734), mapTypeId: Microsoft.Maps.MapTypeId.road, zoom: 4, showBreadcrumb: false, showDashboard: false });
We'll add our own controls to the map; they will change the zoom level and the map type. For this, we will take advantage of the module structure of Bing Maps. The modules are just JavaScript functions that are loaded dynamically by Bing Maps AJAX Control. Apart from better code organization, the modules provide on-demand downloads, reducing the initial script load.
On the flip side, this means that the JavaScript files will be loaded in multiple HTTP requests. If you prefer your scripts merged into a single HTTP request, you can use tools that merge and minimize website assets, such as Asset Bundler in ASP.NET, Sprockets in Ruby, gzipit in PHP, and so forth. In this case, make sure that the custom modules are instantiated after the map is loaded (more information about the Bing Maps modules can be found at http://msdn.microsoft.com/en-us/library/hh125836.aspx).
We'll call our module LearningTheme
, and place it under a learningTheme
folder. Let's write our module within a self-calling, immediately invoked function in the learningTheme.js
file:
(function () { var lbm = window.lbm = {}; lbm.LearningTheme = function(map) { this._map = map; }; Microsoft.Maps.moduleLoaded('lbm.LearningTheme'); })(this);
We have placed our
LearningTheme
module under the namespace lbm
, just as we did with the CSS styles. This way, we expose only one global variable, lbm
.
The module is just a normal JavaScript function with one argument, that is, map
. This is a reference to the Bing Maps map we instantiated earlier, which we'll store into a private variable named as _map
.
Note
We'll use the convention of starting the variable names with an underscore in this book, to indicate that they should be considered as private variables, and not be called outside our module. There are ways to achieve better access restriction for objects in JavaScript, but they are outside the scope of this book.
After the module body, we add the following line, which turns our function into a Bing Maps module:
Microsoft.Maps.moduleLoaded('lbm.LearningTheme');
We then load the module in our window.onload
function by first registering it:
Microsoft.Maps.registerModule('lbm.LearningTheme', 'learningTheme/learningTheme.js'); Microsoft.Maps.loadModule('lbm.LearningTheme', { callback: function() { var learningTheme = new lbm.LearningTheme(map); } });
The registration function accepts two arguments: the name of the module, and its relative or absolute (or remote) location. We then call Microsoft.Maps.loadModule
, which again needs the module name, and an options object. With the latter argument, we can specify what should happen when the module is loaded. In this case, we instantiate it by passing a reference to the map we created earlier.
Let's add the two buttons that will control the zoom level, one with a minus (-) sign that will decrease the zoom level, and one with a plus (+) sign that will increase it. We'll also add a third button that will toggle the map type between road and aerial.
A function within the module's prototype (a private instance method we can call later with the this
keyword) achieves the following:
lbm.LearningTheme.prototype = { _addHeader: function() { var header = document.createElement('div'); header.className = 'lbm-map-header'; header.innerHTML = [ '<button class="lbm-btn" id="lbm-map-btn-zoomin">-</button>', '<button class="lbm-btn" id="lbm-map-btn-zoomout">+</button>', '<button class="lbm-btn" id="lbm-map-btn-maptype">Aerial</button>' ].join(''); this._map.getRootElement().appendChild(header); } };
First, we create a DOM div
element and give it a class name OF lbm-map-header
, so that we can style it later. The markup for the buttons is simple, shown as follows:
<button class="lbm-btn" id="lbm-map-btn-zoomin">-</button>
Again we specify a class (lbm-btn
) for styling and an ID (lbm-map-btn-zoomin
) for controlling its behavior. We place the buttons in an array that we later join with an empty string. This is a common technique for building HTML strings in a JavaScript code, BECAUSE it improves the readability of the code.
At the end, we append the markup to the map's root element. Bing Maps provide a nice utility method to get the latter, getRootElement
. (The list of methods, properties, and events of the map can be found at http://msdn.microsoft.com/en-us/library/gg427609.aspx).
We want the header to be added to the map when the module is loaded; therefore, we call the _addHandler
method in the module's constructor, which now looks like this:
lbm.LearningTheme = function(map) {
this._map = map;
this._addHeader();
};
If you view the index.html
file on the browser, you will not see the default Bing Maps dashboard, but you won't see our controls either. This is because we have not added our styles yet. Let's place them in a learningTheme.css
file inside the learningTheme
folder, as shown in the following code:
.lbm-map-header { position: absolute; top: 1em; left: 1em; width: 100%; color: #fff; z-index: 100; } .lbm-map-header .lbm-btn { font-size: 1em; border: 0; background: #F04C40; color: #fff; display: inline-block; text-decoration: none; width: 2.5em; height: 2em; line-height: 2em; padding: 0; margin: 0 1px 0 0; outline: 0; text-align: center; cursor: pointer; opacity: .8; border-radius: .2em; -webkit-border-radius: .2em; -moz-border-radius: .2em; } .lbm-map-header #lbm-map-btn-zoomin { border-top-right-radius: 0; -webkit-border-top-right-radius: 0; -moz-border-top-right-radius: 0; border-bottom-right-radius: 0; -webkit-border-bottom-right-radius: 0; -moz-border-bottom-right-radius: 0; } .lbm-map-header #lbm-map-btn-zoomout { border-top-left-radius: 0; -webkit-border-top-left-radius: 0; -moz-border-top-left-radius: 0; border-bottom-left-radius: 0; -webkit-border-bottom-left-radius: 0; -moz-border-bottom-left-radius: 0; } .lbm-map-header .lbm-btn:hover { background: #cf343e; } .lbm-map-header .lbm-btn:active { background: #cc1d28; } .lbm-map-header #lbm-map-btn-maptype { margin-left: 2em; width: auto; padding: 0 .8em; }
We make sure that the buttons' container has an absolute position
and a high z-index
value, so that it appears above the map.
Now, we need to add a link to the CSS file to our HTML document, and we'll do this when the module is loaded. This way, if the module is never loaded, then the stylesheet is not loaded either. Again, loading our CSS file on demand means another HTTP request, and if you prefer to have the styles merged into a single file, then you can use the same technique as for the JavaScript files, and skip the following step:
We'll add a link to our CSS file to the head of the page as shown in the following code snippet:
_addCss: function() { var link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('href', 'learningTheme /learningTheme.css'); var head = document.getElementsByTagName('head')[0]; head.appendChild(link); }
First, the preceding function creates a link
element, with a href
attribute pointing at our learningTheme.css
file. Then, it gets a reference to the head
of the document, and it appends to it the newly created element.
As with the buttons markup, we need the stylesheet to be loaded with the module, so we'll call this function in the module's constructor, as shown in the following code:
lbm.LearningTheme = function(map) {
this._map = map;
this._addCss();
this._addHeader();
};
If we open the index.html
file on a browser now, we can finally see our controls, as shown in the following screenshot:

We need our buttons to respond to mouse clicks. Since we added them to the map's root element, we can use the Bing Maps events to add this functionality. Bing Maps AJAX Control provides a rich event system, that can be attached to the map or its entities. Some of the events include click
, dblclick
, keydown
, keypress
, imagerychanged
, and so on.
Let's add our eventing functionality to a private method, which accepts a context argument, as shown in the following code:
_bind: function(self) { Microsoft.Maps.Events.addHandler(this._map, 'click', function(e) { if (e.originalEvent && e.originalEvent.target) { var btn = e.originalEvent.target; if (btn.tagName === 'BUTTON' && btn.className.match(/lbm-btn/) ) { if (btn.id === 'lbm-map-btn-maptype') { self._changeMapType(btn); } else { self._zoom(btn); } e.handled = true; } } }); }
The self
argument passed to the _bind
function will call any functions declared within it in the module's context. This is necessary, because JavaScript changes the meaning of this
inside the handling function from our module to the DOM element firing the event.
We then add an event handler to the map by calling the Microsoft.Maps.Events.addHandler
method, which accepts three parameters—the map
(or its entity, such as a pin or shape), the name of the event, and the handler
function. To the latter is then passed a MouseEventsArgs
object, which exposes a few properties and methods (see the full description available at http://msdn.microsoft.com/en-us/library/gg406731.aspx).
One of these properties is
originalEvent
. It is tied to the DOM event of the clicked element, which can be found by calling e.originalEvent.target
. We then make sure that the elements clicked are, in fact, our buttons, and we call the respective handler depending on their ID, and passing it the button clicked as an argument.
We set the handled
property to the Events
object to true
, telling the map to not call any original handlers attached to the same event.
In order for the events handlers to work, we need to call our binding function in the module's constructor, passing it this
as the context, because this
in the constructor references the module itself. Now the constructor looks as shown in the following code snippet:
lbm.LearningTheme = function(map) { this._map = map; this._addCss(); this._addHeader(); this._bind(this); };
To implement the handlers, we start with the _changeMapType
:
_changeMapType: function(btn) { var options = this._map.getOptions(); if (btn.innerHTML.match(/aerial/i)) { btn.innerHTML = 'Road'; options.mapTypeId = Microsoft.Maps.MapTypeId.aerial; } else { btn.innerHTML = 'Aerial'; options.mapTypeId = Microsoft.Maps.MapTypeId.road; } this._map.setView(options); }
First, we cache the map's current options by calling the getOptions
method. This allows us to change the options object and pass it back to the map later, without having to set all the options.
The handler then checks the text of the button, and it changes the mapTypeId
option to the corresponding Microsoft.Maps.MapTypeId
enum value. At the end it passes the options back to the map's setView
method, which in return changes the map type.
If we open the index.html
file on the browser, and click on the Aerial button, we see how the map changes from the road view to the aerial view. The button's text also changes to Road. Clicking on it again, will change its text back to Aerial and the map view to road. The AJAX Control adds a nice animation when changing the map view; it can be disabled by specifying animation: false
in the options object.
The _zoom
handler looks like this:
_zoom: function(btn) { var zoom = this._map.getZoom(); if (btn.id === 'lbm-map-btn-zoomout') { zoom++ } else if (btn.id === 'lbm-map-btn-zoomin') { zoom-- } var options = this._map.getOptions(); options.zoom = zoom; this._map.setView(options); }
First, we get the map's current zoom value, by calling the getZoom
method, and store it on a zoom
variable. We then increase or decrease the value of this variable based on which button was clicked. The IDs we specified earlier for the buttons come in handy here, BECAUSE we can easily identify the event target by its DOM ID. Please note that the Microsoft.Maps API will ignore any zoom levels outside the established range between 1 and 20.
We then get the existing view options of the map and extend them with the updated zoom level. As in the previous handler, we pass them to the setView
method of the map. After refreshing the index.html
file on the browser, we see how our zoom buttons change the map's zoom level as we click on them.
The Bing Maps AJAX Control is a JavaScript library that adds mapping functionality to a web page. In this chapter, we've learned how to load a map on a page, and how easy it is to customize its appearance and functionality. We've looked at the map methods for getting and setting options, zoom, map type, and so on. We've also introduced the map events, and have added handlers to a custom map dashboard. In the next chapter, we will extend these events to draw polygons on the map.