Performance Problems for putting 700 locations on a map
- I've added to our home page for Silicon Valley Code Camp. The JavaScript is below and is very straight forward (and posted below). I have many people reporting to me that it takes a few seconds (sometimes more) to load, and when they try to do other stuff while it's loading, the browser hangs or worse.
How can I mitigate this problem?
Here is the URL:
http://www.siliconvalley-codecamp.com/
and here is the JavaScript:
var map = null; var layerAttendee = null; var layerSpeaker = null; var LA = new VELatLong(34.0540, -118.2370); var pinPoint = null; var pinPixel = null; function GetMap() {map =
new VEMap('myMap');map.IsBirdseyeAvailable =
false;map.LoadMap();
}
function CreateLayerSpeakers() {layerSpeaker =
new VEShapeLayer();map.AddShapeLayer(layerSpeaker);
}
function DeleteLayerSpeakers() { if (layerSpeaker != null) {map.DeleteShapeLayer(layerSpeaker);
layer =
null;}
}
function CreateLayerAttendees() {layerAttendee =
new VEShapeLayer();map.AddShapeLayer(layerAttendee);
}
function DeleteLayerAttendees() { if (layerAttendee != null) {map.DeleteShapeLayer(layerAttendee);
layerAttendee =
null;}
}
function AddOnePin(layer, lat, lon, firstName, lastName, speakerPictureUrl, isSpeaker) { var pin = new VEShape(VEShapeType.Pushpin, new VELatLong(lat, lon)); if (isSpeaker == true) {pin.SetCustomIcon(
'Images/OrangeCircle.gif'); var description = "<img src='" + speakerPictureUrl + "' originalAttribute="src" originalPath="" + speakerPictureUrl + "" alt='Speaker Picture' /><br/>Speaker: " +firstName +
' ' + lastName;pin.SetDescription(description);
// "<img src='http://peterkellner.net/images/myhead.jpg' originalAttribute="src" originalPath="http://peterkellner.net/images/myhead.jpg" alt='peter' />"); //pin.SetDescription(speakerDetails);}
else {pin.SetCustomIcon(
'Images/BlueCircle.gif');}
//pin.SetTitle(city);layer.AddShape(pin);
}
function pageLoad() { var request = new Sys.Net.WebRequest(); //request.set_url("AttendeeZipCode.ashx?AddPaulKing=true");request.set_url(
"AttendeeZipCode.ashx");request.add_completed(onRequestCompleted);
request.invoke();
}
function onRequestCompleted(executor, args) { // var e = executor.checkError(); (CheckError returns bad result) // if (e) { // alert("error: " + e.message); // } // else {processResults(executor.get_responseData());
// }}
function processResults(theData) {GetMap();
//statusDiv.innerHTML = req.responseText; var dataObject = json_parse(theData);CreateLayerAttendees();
CreateLayerSpeakers();
var points = new Array(); for (var i = 0; i < dataObject.length; i++) { var lattitude = dataObject[i].lattitude; var longitude = dataObject[i].longitude; var firstName = dataObject[i].firstName; var lastName = dataObject[i].lastName; var speakerPictureUrl = dataObject[i].speakerPictureUrl;points[i] =
new VELatLong(lattitude, longitude); if (dataObject[i].isSpeaker) {AddOnePin(layerSpeaker, lattitude, longitude, firstName, lastName, speakerPictureUrl, dataObject[i].isSpeaker);
}
else {AddOnePin(layerAttendee, lattitude, longitude, firstName, lastName, speakerPictureUrl, dataObject[i].isSpeaker);
}
}
map.SetMapView(points);
}
function DisplayLayersByCheckBoxes() { var speakerCheckBox = document.getElementById('ctl00_ctl00_ctl00_blankContent_parentContent_MainContent_CheckBoxShowSpeakers'); var attendeeCheckBox = document.getElementById('ctl00_ctl00_ctl00_blankContent_parentContent_MainContent_CheckBoxShowAttendees'); if (attendeeCheckBox.checked == true) {layerAttendee.Show();
}
else {layerAttendee.Hide();
}
if (speakerCheckBox.checked == true) {layerSpeaker.Show();
}
else {layerSpeaker.Hide();
}
}
function clicked_RedisplayAttendees() {DisplayLayersByCheckBoxes();
}
function clicked_RedisplaySpeakers() {DisplayLayersByCheckBoxes();
}
Peter Kellner, MVP ASP.NET
Answers
- There are known performance issues when displaying a large number of locations on a Virtual Earth map. There are several ways to resolve these issues. The most common way is to use clustering. there are two common clustering methods, client side and server side clustering. Virtual Earth has a new client side clustering feature in version 6.2. Information this can be found here: http://msdn.microsoft.com/en-us/library/cc980909.aspx There is also on an article on how to perform c;ient side clustering in older versions of Virtual Earth here: http://msdn.microsoft.com/en-us/library/cc980909.aspx Client side clustering is usually good for up to 1000 locations. Anything larger you should use server side clustering. Articles on server side clustering can be found here: http://www.viawindowslive.com/Articles/VirtualEarth/ClusteringPinsinVirtualEarth6.aspx
- Marked As Answer byRichard_BrundrittMVP, ModeratorFriday, November 20, 2009 5:20 AM
- Peter, The issue is your adding one pin at a time - the extra calculations around this is adding time per shape. Instead you can add your pins to a simple Array and then using the exact same method as adding a single pin, bulk add all of them.
layer.AddShape(pin); //accepts an Array of VEShapes.
I would modify your "AddOnePin" maybe to just return the VEShape, have those being added to a new Array and then at the end of your JSON parse bulk add them.
Ah, looks like you would need an Array per shape layer - hope you understand what I mean.
[UPDATE - add an example code]
var newShapes = new Array();
//clear existing pins
this._layer.DeleteAllShapes();
//add new pins
for(x = 0; x < locs.length; x++) {
var loc = locs[x];
var newShape = new VEShape(VEShapeType.Pushpin, loc);
newShape.SetCustomIcon("<div class='myPushpin'></div>");
newShapes.push(newShape);
}
this._layer.AddShape(newShapes);
John.
Windows Live Developer MVP - www.soulsolutions.com.au - Follow me at http://twitter.com/virtualearth for news as it happens.- Marked As Answer byRichard_BrundrittMVP, ModeratorFriday, November 20, 2009 5:20 AM
- Edited bySoulSolutionsMVP, ModeratorTuesday, October 21, 2008 10:16 PMAdded sample code
All Replies
- There are known performance issues when displaying a large number of locations on a Virtual Earth map. There are several ways to resolve these issues. The most common way is to use clustering. there are two common clustering methods, client side and server side clustering. Virtual Earth has a new client side clustering feature in version 6.2. Information this can be found here: http://msdn.microsoft.com/en-us/library/cc980909.aspx There is also on an article on how to perform c;ient side clustering in older versions of Virtual Earth here: http://msdn.microsoft.com/en-us/library/cc980909.aspx Client side clustering is usually good for up to 1000 locations. Anything larger you should use server side clustering. Articles on server side clustering can be found here: http://www.viawindowslive.com/Articles/VirtualEarth/ClusteringPinsinVirtualEarth6.aspx
- Marked As Answer byRichard_BrundrittMVP, ModeratorFriday, November 20, 2009 5:20 AM
- Thanks for the reply.
I'm well under 1000 points and still, people complain. Is there anyway to have the browser not hang when the javascript is doing it's thing?
Peter Kellner, MVP ASP.NET - Peter, The issue is your adding one pin at a time - the extra calculations around this is adding time per shape. Instead you can add your pins to a simple Array and then using the exact same method as adding a single pin, bulk add all of them.
layer.AddShape(pin); //accepts an Array of VEShapes.
I would modify your "AddOnePin" maybe to just return the VEShape, have those being added to a new Array and then at the end of your JSON parse bulk add them.
Ah, looks like you would need an Array per shape layer - hope you understand what I mean.
[UPDATE - add an example code]
var newShapes = new Array();
//clear existing pins
this._layer.DeleteAllShapes();
//add new pins
for(x = 0; x < locs.length; x++) {
var loc = locs[x];
var newShape = new VEShape(VEShapeType.Pushpin, loc);
newShape.SetCustomIcon("<div class='myPushpin'></div>");
newShapes.push(newShape);
}
this._layer.AddShape(newShapes);
John.
Windows Live Developer MVP - www.soulsolutions.com.au - Follow me at http://twitter.com/virtualearth for news as it happens.- Marked As Answer byRichard_BrundrittMVP, ModeratorFriday, November 20, 2009 5:20 AM
- Edited bySoulSolutionsMVP, ModeratorTuesday, October 21, 2008 10:16 PMAdded sample code
- Wow! I did what you suggest and it actually slows it down (unless I'm somehow mis timing it). I'm pasting my code below as well as showing two links with and without the new array addition you (John) talked about above. I put a simple JavaScript time and show the results right below the map. what it shows is the old way (one pin at a time), it takes 2.5 seconds. The new way (array add) it takes 3.5 seconds.
Any other suggestions?
http://www.siliconvalley-codecamp.com/DefaultPinArray.aspx - new method using array add of pins (see code below)
http://www.siliconvalley-codecamp.com/DefaultSinglePinAdd.aspx - same code as original with single pin add but with added timer
var map = null; var layerAttendee = null; var layerSpeaker = null; var attendeePins = new Array(); // guess on size var speakerPins = new Array(); // guess on size var LA = new VELatLong(34.0540, -118.2370); var pinPoint = null; var pinPixel = null; function GetMap() {map =
new VEMap('myMap');map.IsBirdseyeAvailable =
false;map.LoadMap();
}
function CreateLayerSpeakers() {layerSpeaker =
new VEShapeLayer();map.AddShapeLayer(layerSpeaker);
}
function DeleteLayerSpeakers() { if (layerSpeaker != null) {map.DeleteShapeLayer(layerSpeaker);
layer =
null;}
}
function CreateLayerAttendees() {layerAttendee =
new VEShapeLayer();map.AddShapeLayer(layerAttendee);
}
function DeleteLayerAttendees() { if (layerAttendee != null) {map.DeleteShapeLayer(layerAttendee);
layerAttendee =
null;}
}
function AddOnePinToSpeakerArray(lat, lon, firstName, lastName, speakerPictureUrl, isSpeaker) { var pin = new VEShape(VEShapeType.Pushpin, new VELatLong(lat, lon)); if (isSpeaker == true) {pin.SetCustomIcon(
'Images/OrangeCircle.gif'); var description = "<img src='" + speakerPictureUrl + "' originalAttribute="src" originalPath="" + speakerPictureUrl + "" alt='Speaker Picture' /><br/>Speaker: " +firstName +
' ' + lastName;pin.SetDescription(description);
// "<img src='http://peterkellner.net/images/myhead.jpg' originalAttribute="src" originalPath="http://peterkellner.net/images/myhead.jpg" alt='peter' />"); //pin.SetDescription(speakerDetails);}
else {pin.SetCustomIcon(
'Images/BlueCircle.gif');}
speakerPins.push(pin);
}
function AddOnePinToAttendeeArray(lat, lon, firstName, lastName, speakerPictureUrl, isSpeaker) { var pin = new VEShape(VEShapeType.Pushpin, new VELatLong(lat, lon)); if (isSpeaker == true) {pin.SetCustomIcon(
'Images/OrangeCircle.gif'); var description = "<img src='" + speakerPictureUrl + "' originalAttribute="src" originalPath="" + speakerPictureUrl + "" alt='Speaker Picture' /><br/>Speaker: " +firstName +
' ' + lastName;pin.SetDescription(description);
// "<img src='http://peterkellner.net/images/myhead.jpg' originalAttribute="src" originalPath="http://peterkellner.net/images/myhead.jpg" alt='peter' />"); //pin.SetDescription(speakerDetails);}
else {pin.SetCustomIcon(
'Images/BlueCircle.gif');}
attendeePins.push(pin);
}
function AddOnePin(layer, lat, lon, firstName, lastName, speakerPictureUrl, isSpeaker) { var pin = new VEShape(VEShapeType.Pushpin, new VELatLong(lat, lon)); if (isSpeaker == true) {pin.SetCustomIcon(
'Images/OrangeCircle.gif'); var description = "<img src='" + speakerPictureUrl + "' originalAttribute="src" originalPath="" + speakerPictureUrl + "" alt='Speaker Picture' /><br/>Speaker: " +firstName +
' ' + lastName;pin.SetDescription(description);
// "<img src='http://peterkellner.net/images/myhead.jpg' originalAttribute="src" originalPath="http://peterkellner.net/images/myhead.jpg" alt='peter' />"); //pin.SetDescription(speakerDetails);}
else {pin.SetCustomIcon(
'Images/BlueCircle.gif');}
//pin.SetTitle(city);layer.AddShape(pin);
}
function pageLoad() { var request = new Sys.Net.WebRequest(); //request.set_url("AttendeeZipCode.ashx?AddPaulKing=true");request.set_url(
"AttendeeZipCode.ashx");request.add_completed(onRequestCompleted);
request.invoke();
}
function onRequestCompleted(executor, args) { // var e = executor.checkError(); (CheckError returns bad result) // if (e) { // alert("error: " + e.message); // } // else {processResults(executor.get_responseData());
// }}
function processResults(theData) { var _StopWatch = new StopWatch();_StopWatch.start();
GetMap();
//statusDiv.innerHTML = req.responseText; var dataObject = json_parse(theData);CreateLayerAttendees();
CreateLayerSpeakers();
var points = new Array(); for (var i = 0; i < dataObject.length; i++) { var lattitude = dataObject[i].lattitude; var longitude = dataObject[i].longitude; var firstName = dataObject[i].firstName; var lastName = dataObject[i].lastName; var speakerPictureUrl = dataObject[i].speakerPictureUrl;points[i] =
new VELatLong(lattitude, longitude); if (dataObject[i].isSpeaker) { //AddOnePin(layerSpeaker, lattitude, longitude, firstName, lastName, speakerPictureUrl, dataObject[i].isSpeaker);AddOnePinToSpeakerArray(lattitude, longitude, firstName, lastName, speakerPictureUrl, dataObject[i].isSpeaker);
}
else { //AddOnePin(layerAttendee, lattitude, longitude, firstName, lastName, speakerPictureUrl, dataObject[i].isSpeaker);AddOnePinToAttendeeArray(lattitude, longitude, firstName, lastName, speakerPictureUrl, dataObject[i].isSpeaker);
}
}
layerSpeaker.AddShape(attendeePins);
layerAttendee.AddShape(speakerPins);
map.SetMapView(points);
_StopWatch.stop();
document.getElementById(
'RunTime').innerHTML = _StopWatch.duration() + " Seconds to Load Map with Data.";}
function DisplayLayersByCheckBoxes() { var speakerCheckBox = document.getElementById('ctl00_ctl00_ctl00_blankContent_parentContent_MainContent_CheckBoxShowSpeakers'); var attendeeCheckBox = document.getElementById('ctl00_ctl00_ctl00_blankContent_parentContent_MainContent_CheckBoxShowAttendees'); if (attendeeCheckBox.checked == true) {layerAttendee.Show();
}
else {layerAttendee.Hide();
}
if (speakerCheckBox.checked == true) {layerSpeaker.Show();
}
else {layerSpeaker.Hide();
}
}
function clicked_RedisplayAttendees() {DisplayLayersByCheckBoxes();
}
function clicked_RedisplaySpeakers() {DisplayLayersByCheckBoxes();
}
// code from: http://www.codeproject.com/KB/scripting/JavaScriptStopwatch.aspx?fid=1526536&df=90&mpp=25&noise=3&sort=Position&view=Quick function StopWatch() { var startTime = null; var stopTime = null; var running = false; this.start = function() { if (running == true) return; else if (startTime != null)stopTime =
null;running =
true;startTime = getTime();
}
this.stop = function() { if (running == false) return;stopTime = getTime();
running =
false;}
this.duration = function() { if (startTime == null || stopTime == null) return 'Undefined'; else return (stopTime - startTime) / 1000;}
this.isRunning = function() { return running; } function getTime() { var day = new Date(); return day.getTime();}
}
Peter Kellner, MVP ASP.NET - Wow that is weird, and for some reason it runs significantly faster in FireFox. I'm not sure what is going on there.
Change your timer to just surround the two lines:
layerSpeaker.AddShape(attendeePins);
layerAttendee.AddShape(speakerPins);
This will tell you the time to actually add the points. Maybe extend your timing to report on each of the steps of the process.
Since you want to solve your scalability issues anyway did you want to use the VE ASP.NET control instead? .Net3.5 is required.
I have a full sample that is lightning fast for 9000 points here:
http://veasp.soulsolutions.com.au/ServerSideClustering/02EROSupport.aspx
Full source code is there to use. Not hard to add some filtering types.
Windows Live Developer MVP - You are right that all the time is in the AddShape call (whether one pin or array of pins). It's roughly 1/160th of a second per pin on my very fast computer (much slower when running on regular hardware). So, 800 pins, about 5 seconds. The demo on your site is blazingly fast, but I'm assuming because you are doing serverside clustering and are only displaying 80 points on the map, or only calling AddShape 80 times per page.
I'm not sure how a server side control could ever do any better than javascript, but maybe I'm missing something.
As an aside, I did some basic comparison's to Google's Map and my first pass seem to show that Google is hugely faster, but no concrete data to back that up.
Peter Kellner, MVP ASP.NET - Back when they first released the bulk pin add I did some benchmarks:
http://www.code-magazine.com/article.aspx?quickid=0804042&page=3
At the time I did find some interesting issue where if the points were not in the current view of the map they actually took longer. I never dug deeper into this, could you try adding your pins with the zoomlevel = 1 versus zoomed in and see if that makes a difference?
Also Johannes did some tests here:
http://johanneskebeck.spaces.live.com/Blog/cns!42E1F70205EC8A96!3835.entry
From all our tests it should only take a 1-2 sec to add your 700 pins. But on the same laptop on your site in IE7 it is taking about 5 sec.
John.
Windows Live Developer MVP - Hello,I have the same problem. I have to use 2500 pushpins and about 3000 polylines(only 2 points/polyline) on a map. Clustering is not possible. Please help me if you know solution for fast pushpin&polyline rendering after zoom&pan like GMAP "MarkerLight", under Bing maps. I'm very flustrated because of lot time to test bad solutions under these slow toys (Bing and GMap and javascript). At last I will draw my polylines&pushpins up on a simple image as a map layer :(.Thank you for your help,Istvan
- If you don't want to cluster that data and want to have everything loaded there is two approaches that can be taken.
Approach 1: Only load the data in the viewable map. This can significantly reduce the amount of data that the map control has to handle. If the user zooms out to a point where too much data would be shown you can also not display that data.
Approach 2: Create a tile layer of your data and overlay it on your map. This will offer the best performance, however it is a bit more work to create. I'm giving a presentation on November 25th at Microsoft in Reading, UK on "Dynamic Tile layers in the Silverlight control" (http://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032428360&Culture=en-GB) . The information I'll be presenting is also applicable to the AJAX control of Bing Maps. If your not in the UK area I'll be posting information regarding dynamic tile layers after the presentation on my blog: http://rbrundritt.spaces.live.com.
Windows Live Developer MVP - http://rbrundritt.spaces.live.com - Funny that you're giving that session, Ricky - I have a presentation on exactly the same topic....!
We should compare notes ;)
Beginning Spatial with SQL Server http://www.apress.com/book/view/1430218290

