Vladimir Agafonkin announced that CloudMade have released Leaflet, a new open source JavaScript library for displaying map tiles as a slippy map. I've used OpenLayers up to now, so I thought I'd take a look at it.
I have a love-hate relationship with JavaScript. It is great that there is a powerful language that runs in browsers, but the environment it runs in is horrible: DOM, various browsers supporting various features, a fiddly run-time that makes debugging horrible etc. Firebug helps.
I decided that a reasonable test was to reproduce a slippy map page I already have to see how it works with Leaflet. That would help me compare with OpenLayers, but does run the risk of me using Leaflet in an OpenLayers way, which I hope I've avoided. It also only covers a small part of what Leaflet offers, but I seems like a start.
My page needs to display a slippy map with markers on it. A side bar will display details and photo for any marker that is clicked and a small message bar will explain the current situation. To get the layout I used a very simple HTML page and a simple style sheet. I tend to not embed styles in HTML or JavaScript, but to use either IDs or Classes to apply styles and declare these in separate style sheets, sometime more than one. I rarely embed JavaScript in HTML either, other than calling functions in events, so JavaScript goes into separate files too. All of these files are available to download, see the links at the end. The Leaflet JavaScript and style sheet are also included. The map is intended to show the locations and information about a series of sculptures around the City of Hull, as part of the Philip Larkin celebrations. They are no longer in place, but I had the data so I used it.
The first part seemed very easy: display a slippy map. The map is displayed in an HTML div. Ours has an id of map. The code to make the map appear is:
var map;
var hull = new L.LatLng(53.775, -0.356);
function initmap() {
map = new L.Map('map');
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
var osmAttrib='Map data © openstreetmap contributors';
var osm = new L.TileLayer(osmUrl,{minZoom:8,maxZoom:18,attribution:osmAttrib});
map.setView(hull,11);
map.addLayer(osm);
}
That is a fairly simple piece of code to display a slippy map. The variables map and hull were defined outside of the function so they are available to other functions as global variables. The single most confusing part is probably the line
map = new L.Map('map')
The first part is a map variable used throughout the code. The second (L.Map) is the leaflet-defined object for a map. This defines how the map object behaves. The last part ('map') is because the div that displays the map has an id of map, which is in the HTML code. The URL includes {s} for the server prefix needed, {z}, {x} & {y} for the zoom, x and y tile coordinate. This allows various tile providers' tile structure to be used. Now the map needs markers.
Adding markers with Leaflet is very straightforward when you have the longitude and latitude for each marker. However, displaying a map with hundreds or even thousands of markers slows things down a lot, so I like to only add the markers that are visible in the currently displayed part of the map. You can get this by calling the getBounds() method of the map object. This returns the north and south latitude and the east and west longitude that describe the edges of the displayed map. Of course this can change as the slippy map is dragged to a new position or the map is zoomed in or out. So the way to handle this change is to respond to map events. The one which seems to fit the bill for us is moveend. This is called whenever the map is moved, including zooming. We supply a function that is called whenever the moveend event is fired. This is achieved by adding the line
map.on('moveend', onMapMove);
Now, whenever the the map is moved our function onMapMove will be called. In our example onMapMove simply calls the function askForToads() which gets the location of the markers we want. I use this extra layer of functions to make debugging easier.
We still don't know where the markers are to be placed, I do that by using AJAX to request the information about all the markers that fall within the bounds of the current map display. askForToads calls the getBounds() method of our map object, extracts the north, south, east and west coordinates and wraps it up into a call to a PHP routine that finds the list of markers and returns this in a JSON format. This returned list is processed by the stateChanged() function which is called as part of the AJAX process. The JSON provides a neat way to return this data, because simply calling eval on the returned string turns it into an array of, in our case, marker data. I then remove any markers already shown and recreate the new markers one at a time, attaching the data for that marker to a data field and recording each marker in an array (toadlayers.push). Working through the array allows me to delete the markers when the map changes.
You now need to respond to each marker that is clicked. This means that the click event needs to be responded to. This needs the line
toadmark.on('click',markerClick);
to ensure that any marker that gets clicked is responded to by the markerClick() function. Here the details from the data field are displayed in the side bar.
Throughout the process the message div displays messages about the progress of fetching the marker info and how many toad markers are being displayed.
This may seem a lot of work to display the markers, but we are making two substantial changes to the normal system of displaying markers, firstly we are only displaying some markers. If there were thousands of potential markers spread across a wide area this would make the process manageable by only displaying a few at a time. Secondly instead of just displaying a popup we are making something outside of the map div change, which opens up a wide range of actions.
How does this compare with OpenLayers? Most of it is simpler. OL provides an interface to AJAX which makes the process a bit simpler. It also puts a collection of markers together into a single layer which is easier to manage - Leaflet makes each marker independent so I had to keep track of the markers I had added. I like Leaflet, and for many things it is something I will use.
You can find the code examples on github If you want the database definition or even the data I can supply that too. You can see the working example here.