Virtual Earth - a Spartan AJAX web control

This article was originally published in VSJ, which is now part of Developer Fusion.
Virtual Earth, Microsoft’s mapping website, has an API that allows you to make use of it within your own web pages. The API is based on the use of a JavaScript control that makes use of no HTML, i.e. it’s an example of Spartan AJAX, and a very convincing one in terms of showing how far you can go in this direction. With the latest version (5) of the control about to be released, now is a good time to look at how it works. Even if you’re not particularly interested in including maps in your application, there are many sophisticated JavaScript techniques incorporated into it that are well worth learning. If you want to know more about Spartan AJAX, see Nothing but JavaScript (VSJ February 2007).

You can try out the mapping provided by Virtual Earth. To find out how to make use of the same data in your own applications you need to visit viavirtualearth.com. Here you will find all you need to get started. The whole API is based around a single JScript object, which you can download and use or access directly.

Older versions of the control could be easily downloaded, and with a little effort could be reformatted and used as a tutorial in Spartan Java. Regrettably the latest version (version 4 at the time of writing) goes even further in attempts to hide the JavaScript by using a self-modifying loader. All you have to do to discover the JavaScript code, however, is to examine the Browser cache, i.e. Temporary Internet files, where you will find the style sheets used and the JavaScript in a file called veapi.ashx. This contains the entire code for the map control as a single line – which causes some text editors a problem. It loads into Word quite easily, and a search and replace can be used to turn semicolons into line breaks. It still isn’t an easy read given an impressive 170 pages of code. It all goes to prove that you can’t really protect JavaScript no matter how hard you try.

Version 4 of the control makes use of radically different JavaScript techniques compared to earlier versions, being much more object-oriented. There is a sense in which each successive version has been a step towards inventing a good object-oriented and HTML-free way of creating controls. There is much to be learned from the Virtual Earth map control. In this article we will look at how to use the control and introduce some new JavaScript techniques for extending it – true inheritance in JavaScript at last?

Getting started

The simplest way to explain how it all works is via the simplest mapping example possible. In keeping with the Spartan AJAX approach used by the control, we will avoid HTML as much as possible. Use NotePad or any HTML editor of your choice to create the following HTML file:
<html>
	<head>
	<title>Map1</title>
	<meta http-equiv="Content-Type"
		content="text/html;
		charset=utf-8">
	<script
	src="http://dev.virtualearth.
			net/mapcontrol/v4/
			mapcontrol.js"></script>
	<script src="map1.js"></script>
	</head>
	<body>
	</body>
</html>
All this does is to load the mapcontrol script and our map1.js custom script. The mapcontrol’s constructor needs the id of a suitable “container” that it will use to display the default map. The simplest thing to use as a container is a Div object. Enter the following JScript in the same directory as the HTML file:
window.onload=function()
{
	MapContainer=new DivAbs(
		document.body,'20px',
		'20px','400px','400px');
	MapContainer.id='myMap'
	map = new VEMap(MapContainer.id);
	map.LoadMap()
}

DivAbs=function(
	parent,left,top,width,height)
{
	DOMObj=document.createElement(
		"div");
	DOMObj.style.position="absolute";
	DOMObj.style.top=top;
	DOMObj.style.left=left;
	DOMObj.style.width=width;
	DOMObj.style.height=height;
	parent.appendChild(DOMObj);
	return DOMObj;
}
The onload event handler acts as the main program as we have to wait for the mapcontrol code to download. Once we have it we can create a Div object, which is defined by the DivAbs function, and then call the VEMap’s constructor. This results in a default map being displayed at the Div’s position. Notice that the MapControl is a JavaScript object, and it works in exactly the same way as the Div object. If you are going to try this out in FireFox 2 you need to include some initialisation at the start of the function as described in the Virtual Earth Wiki.

Methods and properties

Although VEMap looks as if it is a JScript function it really is an object, and if you examine the documentation you will see that it has lots of methods and properties, and indeed there are also lots of helper and utility classes.

For example, the SetCenter method will move the map location to the specified latitude and longitude. If you add a button to the web page:

button1=new button(document.body,
	500,500,100,100,'Go');
button1.onclick=function(){
	map.SetCenter(new
	VELatLong(54.9679,-1.605));}
…with this definition:
button=function(parent,left,top,
	width,height,caption)
{
	DOMObj=document.createElement(
		"button");
	DOMObj.style.position="absolute";
	DOMObj.style.top=top;
	DOMObj.style.left=left;
	DOMObj.style.width=width;
	DOMObj.style.height=height;
	DOMObj.innerHTML=caption;
	parent.appendChild(DOMObj);
	return DOMObj;
}
…then clicking the button re-centres the map on 54.9679N, 1.605W. Notice the use of a VELatLong object to set the position. The new MapControl is even more object-oriented than previous versions, and uses objects wherever possible (see Figure 1).

Figure 1
Figure 1: The user can select satellite pictures, maps or a hybrid, and even a 3D representation

To load the map to an initial location the LoadMap method can also be used:

VEMap.LoadMap(VELatLong, zoom, style,
	fixed, mode, showSwitch);
You can adjust the other viewing parameters using similar methods – just check the online documentation. There is even a PanToLatLon, which can create a degree of animation.

Events

There is also a range of events that you can connect to. The only complication is that the event handler is passed an event object with four properties: view, oblique, error and requestedView. The most useful of these is the view object, which has in turn the following properties:
  • latlong – VELatLong object giving the centre of the map
  • LatLong – VELatLong object giving the location of the mouse click
  • mapStyle – Current map style as a string, valid string results are a, r, h, and o
  • sceneID – If the map style is set to bird’s eye (oblique), the unique identifier of the current bird’s eye scene
  • sceneOrientation – If the map style is set to bird’s eye (oblique), the orientation of the current bird’s eye scene
  • zoomLevel – Current zoom level of the map
A new way of handling events was introduced in version 4. You now have to use the AttachEvent method of the VEMap object. This has the advantage that it maintains a list of event handlers to call, i.e. it supports multiple event handlers per event. There is a DetachEvent method, but to make use of this you have to specify the handler function by name. If you are only specifying a single event handling function then using an anonymous function is easier. For example, to handle the onClick event for the map object you could define an anonymous function:
map.AttachEvent('onclick',
	function(e)
	{alert(e.view.LatLong.Latitude )});
Following this each time the user clicks on the map the message showing the latitude should popup.

To use the event information simply refer to the properties of the e parameter.

function OnClick(e)
{
	alert(e.latitude);
}

PushPins

Moving on from simply panning, zooming and generally controlling the map view it is also possible to add Pushpin markers. The relevant method is:
AddPushpin(VEPushpin)
To create the VEPushpin object we use its constructor:
new VEPushpin(id, location, icon_url,
	title, details, iconStyle,
	titleStyle, detailsStyle);
The pin has to be given a unique id and a latitude and longitude to define its position. All parameters, apart from id, are optional.

And to set the pin to display on the map you would use something like:

pin = new VEPushpin(2,new
	VELatLong(54.9679,-1.605));
map.AddPushpin(pin);
You can remove the pin using the DeletePushPin method.

Dynamic changes

You can set many of the properties of a VEPushpin object in the constructor, and it also has properties that allow you to change its position, title and details text dynamically. However, it doesn’t have a property that lets you change the bitmap used for its icon. You can set a bitmap using the constructor, as in:
pin = new VEPushpin(2,new
	VELatLong(54.9679,-1.605),
	"camera.gif");
…but there are no properties that allow you to change it once set. However, the VEPushpin is a standard DHTML object, and it can be manipulated in the usual way. We can use the VEPushpin’s id to get a reference to the DHTML object:
var pinNode =
	document.getElementById('2');
…and then we can use the getElementsByTagName method to find its img property and set it to a new file:
if (pinNode)
{
	pinNode.getElementsByTagName(
		"img")[0].src = 'camera.gif';
}
This works, but it’s a bit messy. It’s quite easy to do the same job in a more object-oriented way in keeping with the spirit of the Spartan approach used by Virtual Earth. There are two ways of extending an existing JavaScript object. The first is just to add new methods and properties to the existing object. You can always do this using the prototype property, which adds the new method or property to every instance of the object even if they already exist. For example, to add a SetIcon method to all VEPushpin objects you simply write:
VEPushpin.prototype.SetIcon=
	function(icon)
{
	var pinNode =
	document.getElementById(this.ID);
	if (pinNode)
	{
		pinNode.getElementsByTagName(
			"img")[0].src = icon;
	}
}
Notice that the “this” variable can be used to create non-static instances of the method, even though the method is defined outside of the VEPushpin’s constructor – JavaScript is often very subtle in the way it works.

See Figure 2 for different pins.

Figure 2
Figure 2: A custom and a standard pin

True Inheritance?

Adding a method to an existing class is easy, but it isn’t always the way that we want things to work. For example, suppose you need a pin that always has the same icon. If you create this by modifying the existing VEPushpin, then every pushpin on the map will use that same icon. What we need to do is derive a new CustomPin class that has all of the methods and properties of VEPushpin and to which we can add methods and properties without causing any change to VEPushpin. That is, we need to create a class that inherits from VEPushpin. It is generally thought that this sort of inheritance is impossible or at least very difficult to implement in JavaScript. However, using just a little additional machinery, it seems to be fairly easy. One of the problems with inventing a technique such as this is in anticipating all of the different ways that the clever JavaScript programmer can create classes. Is this method foolproof? I can’t prove that it is, but it at least seems well-founded.

The idea is to use JavaScript’s treatment of functions as objects to allow a new object to call another object’s constructor and then add to the object so created. You can think of this as the “always call the base class constructor” rule. For example, the new CustomPin class has the same constructor function signature, although this isn’t necessary in general:

CustomPin=function(f,e,d,h,g,c,b,a){
It then immediately calls the base class constructor:
Obj=new VEPushpin(f,e,d,h,g,c,b,a);
At this point we have an instance of VEPushpin in Obj – more accurately Obj is a reference to a new instance of VEPushpin. Now we can add the new method, not to “this” as we would when creating a new object in the usual way, but to Obj:
Obj.SetIcon=function(icon)
{
We are adding the new method to Obj, not to an instance of CustomPin. However, when we want to access the ID of the pin, we need to deal with the new instance of CustomPin, and in this case we do need to use this.ID:
	var pinNode =
	document.getElementById(this.ID);
	if (pinNode)
	{
		pinNode.getElementsByTagName(
			"img")[0].src = icon;
	}
}
Finally we return Obj as the result of the CustomPin constructor:
	return Obj;
};
This ensures that the new object is a new instance of VEPushpin, plus all of the new methods and properties we have defined.

Putting all this together gives:

CustomPin=function(f,e,d,h,g,c,b,a){
	Obj=new VEPushpin(f,e,d,h,g,c,b,a);
	Obj.SetIcon=function(icon)
	{
		var pinNode =
	document.getElementById(this.ID);
		if (pinNode)
		{
		pinNode.getElementsByTagName(
			"img")[0].src = icon;
		}
	}
	return Obj;
};
Following this definition we can write:
MyPin=new CustomPin(2,
	new VELatLong(54.9679,-1.605));
map.AddPushpin(MyPin);
MyPin.SetIcon('camera.gif');
This creates an instance of CustomPin, adds it to the map and then changes its icon using the newly added method. However, if you try something like:
MyPin=new VEPushpin (2,
	new VELatLong(54.9679,-1.605));
map.AddPushpin(MyPin);
MyPin.SetIcon('camera.gif');
…an error is generated, because VEPushpin doesn’t have a SetIcon method. That is, we really have succeeded in creating a new class that inherits from VEPushpin.

So the recipe for implementing this form of JavaScript inheritance is:

  1. Create your new class complete with suitable constructor
  2. Call the base classes constructor using:
  3. Obj=new baseConstructor();
  4. Use the Obj variable in place of “this” when you are defining new methods and properties,e.g. Obj.newProperty or Obj.newMethod=function(){};
  5. Use “this” within a method declaration when you want to refer to an instance variable defined within the new class
There may be ways of defining classes that cause them not to be inheritable using this simple scheme, but it seems to work.

Drawing on a map

Of course, as the map is just a collection of dynamic HTML elements within a DIV tag generated by the script, we can still use everything we know about DHTML and its extensions. In particular, we can use VML or SVG to draw on the map, but as Microsoft has implemented a set of JavaScript objects, msn.drawing, which will use either VML or SVG as appropriate, it makes sense to use these, or rather the VEMap object methods which make use of them. For example, to draw a polyline you would use:
points=[ new VELatLong(54.967,-1.604),
	new VELatLong(54.968,-1.604),
	new VELatLong(54.968,-1.606),
	new VELatLong(54.967,-1.606),
	new VELatLong(54.967,-1.604)];
poly = new VEPolyline(1,points);
map.AddPolyline(poly);
Notice that points are specified as longitude and latitude rather than as pixels, and to close the drawing you need to repeat the first point as the last. If you want to draw a closed path then use:
poly = new VEPolygon(1,points);
map.AddPolygon(poly);
There are methods and properties that you can use to modify colour, line style and so on – all fairly obvious. What is lacking are more sophisticated drawing tools such as curves and the sort of facilities that are provided by SVG or VML. However, you can use Polyline to draw what look like approximations to curves by using sufficient points, and if you can it is worth restricting yourself to these simple graphics commands – after all they work in a range of browsers without you having to do anything extra.

A Circle object

As an example of the application of some of the ideas described, let’s implement a Circle object – or a VECircle object if you feel confident enough to extend the VE object hierarchy (see Figure 3).

Figure 3
Figure 3: A geographic circle class in action

The circle will be specified as a centre in terms of latitude and longitude and a radius in kilometres. We are going to take this data and generate a set of N points using spherical trigonometry. The details of the calculation can be found on the web – the Virtual Earth Wiki has a number of circle drawing functions.

The first question is how to implement the circle as an object. It makes sense to derive VECircle from VEPolyline because then we automatically get the existing color and width properties and methods. In this case the inheritance follows a slightly different pattern in that we have to first compute an array of points that define the polyline before we call the base constructor. The VECircle constructor starts:

VECircle=function(ID,LatLong,
	Radius,color,width,N)
{
	function DegToRad(angle)
		{return angle* Math.PI/180.0;}
	function RadToDeg(angle)
		{return angle* 180/Math.PI;}
The circle is centred on LatLong with radius given by Radius in kilometres. Colour and width are optional as is N, the number of “sides” the circle has. The first two functions are helper functions to perform angle conversions. To create the array of points we need another helper function:
	function MakeCircle(ID,LatLong,
		Radius,N)
	{
		var points = new Array();
		var lat1 =DegToRad(
			LatLong.Latitude);
		var lon1 =DegToRad(
			LatLong.Longitude);
		var d = Radius/3956;
		if(N==null||N=="undefined")
			var N=40;
		var inc=2*Math.PI/N;
		for (var i = 0; i <= N-1; i+=1)
		{
			var lat = Math.asin(
			Math.sin(lat1)*Math.cos(d)+
				Math.cos(lat1)*
		Math.sin(d)*Math.cos(inc*i));
			lat = RadToDeg(lat);
			var lon;
			if (Math.cos(lat1)==0)
			{
				lon=LatLong.Longitude;
			}
			else
			{
				lon = ((lon1 - Math.asin(
					Math.sin(inc*i) *
					Math.sin(d)/Math.cos(
					lat1)) + Math.PI) %
					(2 * Math.PI)) -
					Math.PI;
			}
			lon = RadToDeg(lon);
			var point =
				new VELatLong(lat,lon);
			points.push(point);
		}
		points.push(points[0]);
		return points;
	}
The constructor uses this to generate the array of points which it then uses to call the base constructor, i.e. the VEPolyline which is returned as the result of the constructor:
	points=new MakeCircle(
		5,LatLong,Radius);
	Obj=new VEPolyline(
		ID,points,color,width);
	return Obj;
};
With this new class we can create a circle and add it to the map using:
MyCircle=new VECircle(5,
	new VELatLong(54.9679,-1.605),0.5)
map.AddPolyline(MyCircle);
Of course as MyCircle inherits from VEPolyline we can also use its methods such as SetColor. For example, to set the circle to red:
MyCircle.SetColor(
	new VEColor(255,0,0,0));
You could argue that it is worth adding an AddCircle method to the VEMap class:
VEMap.prototype.AddCircle=function(a)
	{this.AddPolyline(a)};
Following this you can write:
map.AddCircle(MyCircle);
…with no suggestion that VECircle is derived from VEPolyLine.

There are lots of Virtual Earth topics I haven’t touched on – 3D views and routes for example – but once you understand the general approach they are all fairly easy to use from the documentation. What is perhaps even more interesting is that the Virtual Earth map control is proof that you really don’t need HTML to implement a sophisticated system, and the JavaScript approach to controls is very effective.


Dr. Mike James’ programming career has spanned many languages. The author of Foundations of Programming, he has always been interested in the latest developments and the synergy between different languages.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.”