News

Build stylish, scalable maps using Leaflet.js

Discover how to develop stunning mapping interfaces using open-source geographic data and Leaflet.js

javascriptlogo

DOWNLOAD TUTORIAL FILES

Maps and geographic location data are a big part of our online presence and applications. More often than not, maps tend to look similar, as a single provider generates them. This means your map looks just like everyone else’s.

Let’s take a step back from the existing mapping libraries available and investigate the small but powerful Leaflet.js library. Used by a number of recognisable organisations including Flickr, Meetup and foursquare, Leaflet.js works wonderfully on desktop and mobile devices, and makes use of modern HTML5 and CSS3 techniques, yet it’s still accessible on older browsers.

In this tutorial we will create a data-driven mapping application to plot the location of London train stations. We will see how we can build up the various layers of the map, including the zoom and scale controls, and we’ll obtain our geographic tiles for display from a third-party client. We’ll also incorporate some simple functions to update the style of the generated tiles directly from our map interface.

Import Project

Create a new project or folder in your chosen code editor and import the code from the starting_project directory on the resource disc. This contains the default html page, stylesheets, and jQuery library. Alternatively, you can create your own layout and transfer all of the required asset files to incorporate your own design.

Include Leaflet.js

To begin creating our map, we first need to reference the relevant JavaScript files to include the Leaflet.js functionality. You can download the files if you want to run them locally (available here: monkeh.me/8resh), but we will take advantage of the hosted libraries and call them via the CDN. Place these within the head of your document.

Map container

Create a div element as the container for the map. Any specific ID attribute value can be used for any number of elements – this means that you can include multiple maps on one page, should you so wish. We’ll keep it simple and call our container ‘map_holder’ so it’s easy to recognise and remember. Place this within the main div element.

CloudMade Account

The Leaflet.js mapping tool needs access to tiles – the square images that are pieced together to make the map. There are many tile providers out there, and you can host your own server if you wish. For this tutorial, we will use CloudMade. Head over to cloudmade.com, sign up for a free developer account, and obtain your API key.

Global values

Create a new script tag block, into which we’ll place two global variables. The first is the CloudMade API key, and the second is the default style of map you would like to use – in this instance, we’re going for the Fresh theme. We’ll then add in the jQuery ready() method, inside of which we’ll place the rest of our code.

001 < script type=”text/javascript”>
002 
003 var apiKey = ‘Your CloudMade API key’,
004 styleID = ‘997’;
005 
006 $(document).ready(function() {
007
008 });
009 < /script>

Define boundaries

Our map data will focus on central London, so we have no specific requirements for the user to be able to view outside of a certain area. We can generate two pairs of co-ordinates and set those as the defining boundary box for our application, which will create a specific rectangular area on the map, between the boundaries of which people can view.

001 var southWest = new L.LatLng(50.233152, -6.635742),
002 northEast = new L.LatLng(53.644638, 2.109375),
003 bounds = new L.LatLngBounds(southWest, northEast);

Initialise Map

To display our map on the page, we now need to initialize it and assign it to the map container element. To do so, we’ll create a new map and set the default view using a LatLon pair defaulting to central London. We’ll also set the maxBounds using our bounds value, defined earlier.

generateTileURL

Before we add any tiles to the map, create a function called generateTileURL that will accept the API key and style ID variables. This will generate the required string URL for inclusion in the map, and we can reuse this method a little later on to change the style of the map tiles from a select box element.

001 function generateTileURL(apiKey, styleID) {
002 return ‘http://{s}.tile.cloudmade.com/’ + apiKey + ‘/’ + styleID + ‘/256/{z}/{x}/{y}.png’;
003 }

Add tiles

Using the generateTileURL function with default values, we’ll also create the text for the map attribution layer and set up the default values for our map tiles, including the maximum zoom level. Here, we also detect if the device has a high-definition display. If so, it will use four tiles of half the size to utilise the higher resolution.

001 var cloudmadeUrl = generateTileURL(apiKey, styleID),
002 attribution = ‘Map data © OpenStreetMap contributors.’,
003 tileLayer = new L.TileLayer(
004 cloudmadeUrl, 
005 { 
006 maxZoom: 18, 
007 attribution: attribution,
008 detectRetina: true
009 });
010 
011 tileLayer.addTo(map);

Custom zoom

We added zoomControl false to our map initialization method. By default a control is added to the top-left. We can override this to suit our layout by creating a new zoom control object and specifying the required position from a list of predefined options. We then need to add this to our map object like so.

001 var zoomControl = new L.Control.Zoom({ position: ‘topright’} );
002 zoomControl.addTo(map);

Display scale

We can also create a new scale control object to accurately display the scale of the map. Our attribution layer is positioned down on the bottom-right of the map object, so we’ll position our scale control over on the bottom-left and add this to the map using the same method as we have with the zoom control.

001 var scaleControl = new L.Control.Scale({ position:‘bottomleft’ });
002 scaleControl.addTo(map);

Style selection

Create a new select box form element before the map container, setting the id attribute to styleSelector. This will hold the name and ID values of the styles we would like to select from. This contains the default CloudMade styles, but could also contain any custom styles you may have created especially for your application.

Change style

Add an onchange handler within the jQuery script block. When a new style is selected, this will set the ID value of the chosen style to the styleID global variable, which we’ll pass through to our tile generation function to update the URL. We then call the setUrl() method to update this value and regenerate the tiles for display.

001 $(“#styleSelector”).bind(“change”, function() 
002 {
003 if ($(“#styleSelector”).val() !== “”) {
004 styleID = $(“#styleSelector”).val();
005 var revisedURL = generateTileURL(apiKey, styleID);
006 tileLayer.setUrl(revisedURL);
007 }
008 });

Import Data

Our application needs to read the data we have about the London train stations. The project contains a file called stationData.js, which includes the JSON data. This data has the latitude, longitude and name of each station, which is perfect for plotting any markers on the map. Reference this file in the head of the HTML document.

Generate markers

Create a new function call addMarkers. This will accept the map object and the stationData in JSON format. Looping over the data, we will create a new Marker object for each station, setting the latitude, longitude and title, as well as binding a Popup window to also display the title string, complete with any HTML markup that may be included.

001 function addMarkers(map, stationData) {
002 for (var i = 0; i < stationData.length; i++) {
003 var a = stationData[i];
004 var popupDetail = a[2];
005 var title = popupDetail.replace(/(<([^>]+)>)/ig,””);
006 var marker = new L.Marker(new L.LatLng(a[0], a[1]), { 
title: title });
007 marker.bindPopup(popupDetail);
008 map.addLayer(marker); }
009 } 

Add markers

Place a reference to the addMarkers function within the main mapping code, after the zoom and scale controls, sending through the required parameters. This will generate the markers and add each one onto the map.

001 addMarkers(map, stationData);

Station selector

Create a new select form element next to the style selector. Set the id attribute for this to stationSelector and provide a default value for display. We’ll use this to select a station, which in turn will prompt the map to pan, zoom and centre itself upon the selected marker.

001 < select id=”stationSelector”>
--- Select Station ---
< /select> 

Populate list

Within the jQuery block, let’s once again loop over the data available from the stationData JSON. We’ll create a new option tag block with every loop, setting the custom data attributes with the station’s geographic co-ordinates, and the name of the station for visual reference. Finally, we’ll append this string to the select element to display the data.

001 var stationOptions = “”;
002 
003 for (var i = 0; i < stationData.length; i++) {
004 stationOptions += ‘
’ + stationData[i][2] + ‘’; 
005 }
006
007 $(“#stationSelector”).append(stationOptions);

Set view

Add a new onchange function that will obtain the latitude and longitude from the data attributes for the selected station. This function will then call the setView() method directly on the map, passing in the
co-ordinates and setting the zoom level from the maxZoom value we added when instantiating the tile layer.

001 $(“#stationSelector”).bind(“change”, function() 
002 {
003 var selectedStation = $(“#stationSelector :selected”);
004 if (selectedStation.attr(“data-latitude”) !== undefined 
005 && selectedStation.attr(“data-longitude”) !== undefined) {
006 var thisLat = selectedStation.attr(“data-latitude”);
007 var thisLon = selectedStation.attr(“data-longitude”);
008 map.setView([thisLat, thisLon], map.getMaxZoom());
009 } else {
010 map.setView(latlng, 1);
011 }
012 });

Using plug-ins

There are many open-source plug-ins for integration with the Leaflet mapping library, available from the plug-in list: leafletjs.com/plugins.html. We’ll use the marker cluster plug-in to provide a much nicer, simplified view of our generated markers on the map. Download the plug-in code and export the contents of the dist folder into your project. The full code for this step can be found on the resource disc.

Revise Markers

With the cluster plug-in added to our page, we must make some small changes to the addMarkers function. Firstly, we’ll create a new MarkerClusterGroup object into which our markers will be placed. Finally we’ll add the populated markers object as a layer onto the map, instead of the individual station markers.

001 function addMarkers(map, stationData) 
002 {
003 var markers = new L.MarkerClusterGroup();
004 for (var i = 0; i < stationData.length; i++) {
005 var a = stationData[i];
006 var popupDetail = a[2];
007 var title = popupDetail.replace(/(<([^>]+)>)/ig,””);
008 var marker = new L.Marker(new L.LatLng(a[0], a[1]), { title: title });
009 marker.bindPopup(popupDetail);
010 markers.addLayer(marker); 
011 }
012 map.addLayer(markers);
013 }

Improved visuals

With the addition of the marker cluster group, the mapping interface has been improved. The markers are now grouped in clusters based on the generated polylines from each marker’s geographic location, and the clusters will update and animate whenever we zoom in our out of the map.

×