I want to tell you about a simple trick that will speed up the loading of map tiles from ArcGIS Server tiled map services in all browsers. We’ve recently incorporated this into LocalView Fusion and it requires only a handful of ingredients:
- A tiled map service running on a server you control.
- A few lines of JavaScript
- Some extra “A” records (subdomain names) for the domain name of your site (use your hosts file if you are developing this and wish to test it)
The problem: Blocking
Browsers can only make a limited number of HTTP requests at once. In modern browsers, like Internet Explorer 9, Chrome and Firefox 4, up to 6 HTTP connections can be made per host name (although in FF at least you can configure this). So if your tiled map service is running on a server with the address like http://www.myserver.com, then these browsers can send up to 6 tile requests at once before they have to stop and wait for the responses to come back.
If more than 6 requests are sent to the same host name, the browser will wait for responses to be returned before sending requests 7, 8, 9 and so on. This is called “blocking”.
However, a browser will usually need to display more than 6 tiles at once to show a map. For larger resolution screens there may be 20 or 30 tiles making up the full map display. So after the first 6 tiles have been requested, the browser will “block” until it can send more tile requests.
The solution: multiple subdomains, and some code
Let me first make it clear that this is not a new trick, I just haven’t seen it implemented in an ESRI web API client yet. Google, Bing and Open StreetMap tiles are routinely requested from multiple subdomains in clients and APIs for those services: for example the OSM tile URLs are a.tile.openstreetmap.org, b.tile.openstreetmap.org, c.tile.openstreetmap.org and so on. This allows 6 tiles to be requested from each subdomain: 18 at once. (Older browsers may have a lower concurrent connection limit: IE7 is limited to two per subdomain for example).
This trick can be applied to ArcGIS Server map services as long as you have control over the registering of the extra “A” records for your domain (a.myserver.com, b.myserver.com and so on). The rule of thumb is that 3 or 4 “A” records will speed up requests but beyond this diminishing returns apply due to the extra burden of DNS lookups.
So – step one: register 4 extra “A” records for the domain where your server is running. In our example these would be:
a.myserver.com
b.myserver.com
c.myserver.com
d.myserver.com
The code
My example here is based on the JavaScript API but there is no reason why the same approach wouldn’t work in Silverlight or Flex, as long as you can implement a custom layer type using those APIs. You can do this easily in the JavaScript API as described in this article on the ArcGIS Resource Centre.
It is easy to extend the ArcGISTiledMapServiceLayer class in the ESRI JavaScript API, thanks to the Dojo framework upon which the API is built. By defining a new class that inherits from a base class, and implementing an override for the properties and methods you wish to change, the amount of code required can be kept to a minimum.
To make multiple requests to the same map service, the code will need to do the following:
- Read the service metadata from the “normal” map service URL as usual. This is the URL passed into the constructor method of the ArcGISTileMapServiceLayer class.
- Define new properties for the list of server URLs to use, and to keep count of which one each tile request is using.
- Override the getTileUrl method, to loop through the list of subdomains in turn and make a request from the next one in the list.
Here is a code sample of the custom layer class as used in LocalView Fusion:
dojo.provide("CustomLayer"); //define the namespace and class dojo.declare("CustomLayer", esri.layers.ArcGISTiledMapServiceLayer, { /* Extends the ESRI class by adding a list of servers. /* This needs to be manually populated when * the object is created. */ servers: [], serversLength: -1, serverIndex: 0, getTileUrl: function (level, row, col) { var idx = 0; //check how many URLs there are. if (this.serversLength < 0) { this.serversLength = this.servers.length; } //if there is more than one URL, get the next in the list. if (this.serversLength > 0) { // Multiple urls idx = this.serverIndex++ % this.serversLength; return this.servers[idx] + "/" + this.url + "/tile/" + level + "/" + row + "/" + col + ".png"; } //if we don’t have extra URLs, just use the default one. else { // Original single url return this.url + "/tile/" + level + "/" + row + "/" + col + ".png"; } } });
And here is a sample of how it might be used in an application:
var basemap = new CustomLayer( "http://www.myserver.com/ArcGIS/rest/services/MapServiceName/MapServer" ); basemap.servers = [ "http://a.myserver.com/ArcGIS/rest/services/MapServiceName/MapServer", "http://b.myserver.com/ArcGIS/rest/services/MapServiceName/MapServer", "http://c.myserver.com/ArcGIS/rest/services/MapServiceName/MapServer" ]; map.addLayer(basemap); //assuming "map" is the name of our map object.
To conclude
In our testing, using a modern browser like Firefox 4, Internet Explorer 9 or Chrome, the effect is that ArcGIS Server tiled maps load in the browser much faster than before, up to 24 tiles coming in at once compared to the batches of 6 you would normally get with those browsers.
If you use IE7 this will be less impressive as it uses a strict implementation of the HTTP 1.1 spec, and can only make two requests per host – even so, this will fetch 8 tiles at once using this method.
If you’re using IE6, well….I don’t suppose faster tile loading is going to make much difference as the general HTML rendering and script execution performance is so bad to start with!
This functionality will be in the next release of LocalView Fusion, but if you are running ArcGIS Server with a registered domain name and can make the edits I’ve described here to your application, then there’s nothing stopping you implementing it right now.
No sooner had I posted this than I was made aware that the JavaScript API has had this capability since version 1.1, and a custom TiledMapServiceLayer implementation isn’t necessary. Thanks to Rahul Ravikumar on the ArcGIS Server Java REST team at Esri for pointing that out.
You can start using this feature by simply adding an optional second argument to the ArcGISTiledMapServiceLayer constructor, as follows:
var myLayer = new esri.layers.ArcGISTiledMapServiceLayer(myServiceURL, { tileServers:[ "http://a.myserver.com", "http://b.myserver.com", "http://c.myserver.com" ] } );
For more details see the API Reference on the ArcGIS Server Resource Center at esri.com.
Everything else in my original post still stands, but hopefully this makes it even easier to implement than my initial suggestion.