This article is part three in a series that explores the next generation clustering techniques for Virtual Earth. In part one Clustering a million points on Virtual Earth using AJAX and .Net we explored the general concept and made it work with ajax and vb.net. In part two Clustering Virtual Earth with MS AJAX and C# we refined the server side code with a complete rewrite in C# and we introduced ASP.NET AJAX web services. In this article I focus on upgrading to Virtual Earth 5 and using the latest Object Orientated approach to Javascript from ASP.NET AJAX. I show I better way to design your Javascript and introduce a shape differential to only add and remove shapes as required to the map.
*Note: The server side code, and as such the core clustering logic, has not changed. The changes are purely client side in the javascript files.
In the very first article we learnt why we need clustering for large numbers of points and also for smaller numbers of point in the close proximity.
(Over 2 min to render on client)
(Under 2 sec to render on client)
Virtual Earth Version 5
The release of VE5 changed much of the API for the better. The performance benefits and the full support for Firefox (including 3D) are great reasons to upgrade. The key areas that effect the clustering demo here are the new shapes layers, the events and the infobox.
The snippets I show are from the source code that can be downloaded from the link at the end, as such varible names and classes may look unfamiliar at first. Open up /js/SoulSolutions.Demo.Map.js for the full version.
Shapes
Pushpins, polygons and polylines are now all types of shapes.
var newShape = new VEShape(VEShapeType.Pushpin, loc);
shapes get added and delete from shapelayers,
this._layer.AddShape(item);
this._layer.DeleteShape(item);
shapelayers are added to the map.
this._layer = new VEShapeLayer();
this._map.AddShapeLayer(this._layer);
You now have methods to set properties of shapes like:
this._currentpin.SetTitle(result.Title);
this._currentpin.SetDescription(result.Details);
You can get access to an existing shape object from its ID
var popupShape = this._map.GetShapeByID(e.elementID)
So we no longer have to store a separate array of information about the pins and resort to hacks to find our pins on the map. This is great news.
Events
The whole event model has changed so we can now attach to any event from within VE or its DOM objects. We can still attach to the common VE events like:
this._map.AttachEvent("onchangeview", this.GetPinDataDelegate);
But now we can easily attach to all mouse events for shapes
this._map.AttachEvent("onmouseover", this.PinHoverDelegate);
this._map.AttachEvent("onclick", this.PinHoverDelegate);
Making hacks like "clickable pins" a thing of the past.
Infobox
The infobox, or what we used to call the "popup", has also changed. Although we can now customise part of the look and feel there are some limitations:
http://www.soulsolutions.com.au/Articles/VirtualEarth5CustomInfobox/tabid/106/Default.aspx
In the demo code I no longer hack some of the old functions required previously to show dynamic content.
Object Orientated Javascript
As the name says ASP.NET AJAX offers us the ability to write our javascript code as objects, although the syntax is a little different to c# it really makes our code neat and modular. The comments notation used is supported by the upcoming Visual Studio 2008 that offers intellisence built from these. Read all about Classes, Members and Namespaces, Inheritance, Interfaces, Enumerations and Reflection here:
http://ajax.asp.net/docs/tutorials/EnhancingJavaScriptTutorial.aspx
http://weblogs.asp.net/bleroy/archive/2006/05/01/ScriptDoc_3A00_-document-your-Atlas-classes.aspx
I have setup two classes:
- Map - the class that controls the map (eventually this could actually inherit from VEMap and override it)
- MapArgs - a class that contains all the initialisation variables for VE.
As usual I have made a javascript "code behind" file - default.aspx.js that has the page event handlers to start and dispose the new map object.
Shape (pin) differential
In previous versions of this demo as you pan around the map all the pushpins (V4) were deleted and then all the pins added. The shape differential logic looks at comparing the current shapes with the new set and only removing and adding shapes as required. It removes the flicker of the previous code and potentially increases performance (I haven’t benchmarked though).
_OnMapDataSucceeded: function(results) {
///
/// Receive data for map.
///
/// The webservice result object - Optomised CSV string
//decode pins
var result=results.split(",")
var locs = Utility.decodeLine(result[0]);
//add new pins
for(x = 0; x < locs.length; x ) {
var loc = locs[x];
var bounds = result[x 1];
var drawn = false;
//see if it is in the current drawn pins
for(i = 0; i < totalpins; i ) {
var currentshape = this._layer.GetShapeByIndex(i);
if (currentshape.Bounds == bounds) {
drawn = true;
currentshape.Match = true;
break; //shortcut loop
}
}
if (!drawn) {
var newShape = new VEShape(VEShapeType.Pushpin, loc);
newShape.Bounds = bounds;
newShape.Drawn = drawn;
newShape.Match = true;
//set custom pin.
newShape.SetCustomIcon("
");
this._layer.AddShape(newShape);
}
}
//remove all existing pins on matches from the screen
var totalpins = this._layer.GetShapeCount();
for(x = (totalpins-1); x >=0; x--) {
var currentshape = this._layer.GetShapeByIndex(x);
if (!currentshape.Match) {
this._layer.DeleteShape(currentshape);
}
//set match back to false for next time we pan the map.
currentshape.Match = false;
}
}
The key here is that all shapes are verified to see if they are already on the map before they are added and unmatched shapes are deleted. When we change zoom levels we clear all shapes from this layer. For those wondering how I added my own custom properties to the shapes, it is done like this:
//Setup additional storage for shapes
VEShape.prototype.Bounds = "";
VEShape.prototype.Drawn = false;
VEShape.prototype.Match = false;
Sample Source Code
Download Source Code Here
You must download and install MS ASP.NET AJAX to run this code.
The sample code for this article gets it data from an XML file of Australian postcodes. I have cached this generated data in memory for performance, for small sets of static data this maybe an option for you. For larger sets of data I recommend storing the data in a database allowing you to query based on the map bounds.
Conclusion:
Version 5 of Virtual Earth drastically reduces the javascript code and "hacks" required to get this working but it is not without issues. The removal of any mechanism within birdseye mode to get a simple bounding box means we have to resort to storing an approximate centre location from the previous scene. 3D mode, by design, doesn't support some of the DOM and CSS type tricks which limits some of the functionality around the info box. I'll explore this in future articles.
The object orientated notation provides a cleaner mechanism and brings concepts more familiar with C# developers to javascript. I highly recommend you use this approach and store each class in a seperate file while in developement. Add as many comments as you can, then as part of your deployment, strip all comments and whitespace and merge into a single file for performance. If you follow the guidelines of marking private varibles and functions with an underscore it opens up the oppotunity to replace these names with shorter automated names as part of the deployment also.
Future thoughts:
The example code was trimmed down in functionality to keep it simple and focus on the basics.
- The announcement of spatial support in SQL Server 2008 may make the database side of this logic even better. Although this demo used a cached XML file typically the data would be queried based on the map bound from SQL server. Being able to limit the amount of data from the data tier is what makes this scale to millions of data points.
- With the new layer functionality it may be possible to request your clustered data on a per tile basis, this could potentially work better than full screen differential as we could add and delete entire layers based on bounds instead of shape by shape compares.
- I'm going to play with dynamically sized shape icons (pins) based on the cluster size, what I would like to have something more like the non clustered picture above, where it is obvious where the density of data is while still have awesome performance and useability.
Have a comment or used this code on your site? Why don't you tell me about it at my blog