3. Adding Data to Leaflet Maps

A quick note about the practicals...

Remember to use Mozilla Firefox to do these practicals. Please do NOT use Microsoft Internet Explorer / Edge, this is not suitable for web development. Coding should be done in Notepad++

You do not have to run every bit of code in this document. Read through it, and have a go where you feel it would help your understanding. If I explicitly want you to do something, I will write an instruction that looks like this:

This is an instruction that tells you something to either think about, or do.

Shortcuts: Part 1 Part 2

In which we learn about objects, and conditionals JavaScript, then use some GeoJSON to make an interactive world map!

Back to JavaScript

On a few occasions last week, we dealt with things that I did not explain at the time. We are going to have a look at some of those today - in particular scope and conditional statements. After that, we will get stuck back in to events by making an interactive map using an external GeoJSON dataset of population:

Scope in functions

One of the most confusing things about using functions in JavaScript is the idea of scope, which dictates which variables and functions are able to be from which parts of your code. The rule is that things defined inside a function cannot be accessed from anywhere outside the function, because the variable is only defined in the scope of that function.

Confusing? well, yes, but don’t worry, I’m going to explain it…

Consider this code:

//define a variable in the global scope
var number = 6;
 * Define a function in the global scope
 * This function returns the square of a number
function square() {
	//calculate the square of a number and return it
	return number * number;

//calculate the square of the number and open a box with the answer
console.log( square() );

In this case, two things are defined in what we call the global scope: var number and function square. The global scope simply means that they are not contained within any other blocks of code (i.e. within a function). Functions and variables in the global scope can be accessed by any code whatsoever, which is why it’s called global. This code is correct (if a little contrived and inelegant) and will work.

Now consider this similar code:

 * Some function or other
function defineNumber(){
	//define a variable in the scope of the function
	var number = 6;

 * Define a function in the global scope
 * This function Returns the square of a number
function square() {

	//calculate the square of a number and return it
	return number * number;

//run the function to define the variable number

//calculate the square of the number and write to the console
console.log( square() );

In this case, number has been defined in the local scope, inside the defineNumber() function. Because of this, the console.log() statement will not be able to see it, because that particular variable only exists inside defineNumber().

This is why it’s often best to make sure that you pass values to functions as arguments - that way you know that it has access to what it needs!

Here are those terms again:

global scope A variable or function that is not defined inside a function, and so is accessible from anywhere in the JavaScript
local scope A variable or function is defined inside a function, and so is only accessible from within the function

We can see how we have already used this approach, by looking at our code from last week. At the start of the <script> section, we defined three variables:

var map, marker, transformer;

This means that these three variables exist within the global scope, even though we don’t assign any values to them until later on (in initMap(), onMapClick() and initMap() respectively).

I know this sounds complicated, but don’t worry if you don’t quite understand it - once you see it for real a few times it’ll make perfect sense - I promise!

Objects in JavaScript

I have referred to javaScript objects a few times already in this course. For the moment, we are not going to get too bogged down with the ins and outs of objects, but it is important that you understand what they are and how to use them. Basically, an object is a bit of code that represents a thing in real life (a cat, a tree, a pair of pants, whatever). Objects have relevant properties and functions associated with them. A cup, for example could be represented by a cup object, with properties such as color (blue), material (china) and capacity (0.3l); and functions such as fillUp(), empty() and clean().

You will come across objects frequently when working with JavaScript, with examples we have already seen including:

map Representing the map from the Leaflet library
tilelayer Representing the tiles that can be displayed in the background of a map
marker Representing a marker for a point on a map
popup Representing a popup for a marker on a map

Objects in javascript are defined using curly braces { } containing a series of properties that are defined using the pattern {property1: value1, property2: value2}. The property name is followed by a colon (:) then the corresponding value, and multiple properties are separated by commas. Here is an illustration:

image Image from json.org

And here is what our cup example from above would look like:

var cup = { 
	color: "blue", 
	material: "china", 
	capacity: 0.3, 
	fillUp: function(){...},	// the ... would contain the function code
	empty: function(){...}, 	// the ... would contain the function code
	clean: function(){...} 		// the ... would contain the function code

Objects are generally created using a constructor, which is a function that creates and returns an object based upon the arguments that you give to it. For example, the constructor for the cup might be like:

var myCup = Cup("blue", "china", 0.3);

The above would return exactly the same object as above, but means that the programmer simply needs to set the properties that they are interested in, they do not have to load in the functions themselves, and can even ignore some properties and just accept the defaults.

Once you have an object, you can access its properties and functions like this:

console.log( myCup.capacity );	//prints 0.3

myCup.empty();	//calls the empty() function of myCup

To link this to the code we have already been using, let’s look at the constructor for a map object that we have been using:

map = L.map('map').setView([53.466502, -2.235387], 3);

The constructor in this statement is L.map(), which requires that you give it the id of the div in which you would like Leaflet to build the map. Once the map has been built, you have access to a number of functions that are members of the object, one of which is .setView(), which takes in two arguments: a coordinate pair upon which to centre the map, and a zoom level. These two sections have been written as a chain of JavaScript commands for ease of reading, though could be just as easily written separately:

// create the map
map = L.map('map');

//set the centre and zoom level
map.setView([53.466502, -2.235387], 3);

Have a look back at week2.html and find the constructors that you used for map, tilelayer, marker and popup

Object Literals in JavaScript

Whilst Constructors will be the most common way of creating objects in JavaScript it is sometimes convenient to simply write out the object yourself (e.g. if it is a very simple object). For example, coordinates are stored in Leaflet using a LatLng object, which can be made using a constructor like this:

var latlng = L.latLng(50.5, 30.5);

Where the constructor LatLng() takes two arguments, the latitude and the longitude. However, the pbject that it returns simply looks like this:

{lat: 50, lng: 30}

As a result, we might as well not bother with a constructor and just write the object literal (as in, literally just write the object out):

var latlng = {lat: 50, lng: 30};

This is exactly the same as the first example, and can be a little more convenient (and is slightly more efficient, as it saves a function call).

Conditional Statements in JavaScript

Another type of statement that we glossed over last week was the conditional statement, which is a fundamental part of JavaScript that allows you to gain greater control over your code. Today we’re going to take a look at an example of this - an if statement, but before that, we need to take a look at some more operators:

Operator Explanation Symbol(s) Example
identity operator Does a test to see if two values are equal to one another, and returns a Boolean. === myVariable = 3;

myVariable === 4; (false)
negation, not equal Returns the logical opposite value of what it preceeds; it turns a true into a false, etc.

When it is used alongside the equality operator, the negation operator tests whether two values are not equal.
!, !== myVariable = 3;

!(myVariable === 3); (false)

myVariable = 3;

myVariable !== 3; (false)
greater than, less than these operators compare the operands and determine whether the one to the left is greater than (>) or less than (<) the one on the right.

When combined with an ‘equal’ symbol (>=, <=), the meaning changes to greater than or equal to or less than or equal to respectively.
>, >=, <, <= var2 > var1

var1 <= var2
increment, decrement ++, -- Adds one to and subtracts one from its operand respectively. If used as a prefix operator (++x), returns the value of its operand after adding one; if used as a postfix operator (x++), returns the value of its operand before adding one. If x is 3, then ++x sets x to 4 and returns 4, whereas x++ returns 3 and, only then, sets x to 4.

if statements

Here is the if statement that we used last week:

if (marker){

This says: if the variable marker already has something in it (i.e. is not null) then remove the existing marker from the map, otherwise do nothing. This is a common type of conditional called an if statement, which is used in order to determine whether or not a particular block of code should run. Their basic form is like this:

if (condition) {
  //statement 1

Let’s break that down:

  • if tells the browser a decision needs to be made based upon the following condition.
  • parentheses (( )) then identify the condition that is to be tested.
  • curly braces ({ }) then identify the block of code that runs if the condition evaluates to true.
  • //statement 1 is therefore in place of the code that would run if the if the condition evaluates to true.

Here’s an example using one of the operators listed above:

if (name !== "Jonny") {
	alert("who are you?");

What does the above code block do?

Falsy values

One more thing that you need to understand in JavaScript is why marker would evaluate to false just because there is nothing stored in it yet. This is because there are number of values that considered to be the same as false. These are called falsy values, and are listed below:

false The boolean value false
undefined A variable that has not been defined using a var statement
null A variable that has been defined but has no value stored in it
0 The number 0
NaN (Not a Number) This occurs when you try to treat a variable with a non-number type (e.g. a String) as if it was one
"" An empty string

marker is therefore null before the first time the user clicks on the map (as no marker objects have been stored in it), so this statement:

if (marker){

is equivalent to:

if (marker !== null){

if...else if...else statements

if statements can also be expanded to allow for multiple options, by adding else if and/or else statements:

if (condition1) {
	// statement 1;
} else if (condition2) {
	// statement 2
} else if (condition3) {
	// statement 3
} else {
	// statement 4

else if allows unlimited extra conditions to be tested, whereas else simply allows for a default action in case all of the conditions evaluate to false. Similarly condition2 will only be tested if condition1 evaluates to false, as soon as one evaluates to true the rest of the if statement will be ignored. So, for example, consider this code, which is designed to customise a web page for different languages:

// set the language for the website
var language = "English";

// greet the visitor with an alert box
if (language == "German") {
	alert("Guten Tag");
} else if (language == "English") {
} else if (language == French) {
} else {
	alert("Sorry, I don't speak" + language);

In this case, the language variable is set to "English", and so the first condition (language == German) will evaluate to false, and the associated code block will be ignored. The second condition (language == English) will therefore be tested, which evaluates to true, and so the associated code block alert("Hello"); will run. Because a condition has now evaluated to true, all subsequent else if statements and the else statements will be ignored - the conditions will not even be tested.

What would happen if I changed the value of language to "French"? What about "Spanish"?

Part 2

Introduction to GeoJSON

GeoJSON is the primary data format for GIS data on the Web these days. It is based upon JSON, which stands for JavaScript Object Notation and is a lightweight data-format that it is easy for humans to read and write, and it is easy for machines to parse and generate. It is also used across almost all programming languages (in spite of the name), and is something that you will be getting quite familiar with throughout this course!

JSON is called JavaScript Object Notation because it is based upon existing JavaScript data structures (even when it is used in other languages). JSON is data stored as JavaScript Objects in plain text!

Just as with the text versions of the JavaScript Objects we have been using to make our maps, is made up of an unordered set of name/value pairs, stored inside curly braces { }. Each name is followed by : (colon) and the name/value pairs are separated by ,(comma). Here is an illustration:

image Image from json.org

GeoJSON is no different to normal JSON, it is simply a certain set of name:value pairs that are designed to describe GIS data. Here is an example of a GeoJSON representation of my office:

  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [53.466502, -2.235387]
  "properties": {
    "name": "Jonny's Office"

Pretty simple, right? GeoJSON supports geometry stored as:

  • Point, LineString and Polygon (for single features)
  • MultiPoint, MultiLineString, and MultiPolygon (for multiple features) e.g. if a single feature is made up of more than one geometry - such as a lake that contains an island, or a hotel that takes up two buildings
  • FeatureCollection, which is a special container used to hold sets of objects of the above types.

In this case, I have made you a GeoJSON file that contains the population of each country in the world, the first few lines look like this (with explanatory comments added by me):

	"type": "FeatureCollection",	// it is a feature collection, containing a collection of Polygons and MultiPolygons (one for each country)
	"name": "countries",		// the name of the feature collection
	"crs": { 			// the coordinate reference system of the feature collection, encoded using a special code
		"type": "name", 
		"properties": { 
			"name": "urn:ogc:def:crs:OGC:1.3:CRS84" 
	"features": [{ 				// a container for the features in the feature collection 
		"type": "Feature", 		// the first feature in the features list
		"properties": { 		// a container for the properties of the feature
			"NAME": "Fiji", 	// the name of the feature (country)
			"POP_EST": 920938 	// the estimated population of the feature (country)
		"geometry": { 			// a container for the geometry (shape) of the feature
			"type": "MultiPolygon", // the type of geometry for this feature
						// the list of coordinates that describe the feature
			"coordinates": [ [ [ [ 180.0, -16.067132663642447 ], [ 180.0, -16.555216566639196 ], [ 179.364142661964138, -16.801354076946883 ], [ 178.725059362997115, -17.012041674368039 ], [ 178.596838595117134, -16.63915 ], [ 179.0966093629971, -16.433984277547403 ], [ 179.413509362997104, -16.379054277547404 ], [ 180.0, -16.067132663642447 ] ] ], [ [ [ 178.12557, -17.50481 ], [ 178.3736, -17.33992 ], [ 178.71806, -17.62846 ], [ 178.55271, -18.15059 ], [ 177.93266, -18.28799 ], [ 177.38146, -18.16432 ], [ 177.28504, -17.72465 ], [ 177.67087, -17.38114 ], [ 178.12557, -17.50481 ] ] ], [ [ [ -179.793320109048636, -16.020882256741224 ], [ -179.917369384765294, -16.501783135649397 ], [ -180.0, -16.555216566639196 ], [ -180.0, -16.067132663642447 ], [ -179.793320109048636, -16.020882256741224 ] ] ] ] 
	// next feature...

It is not important that you memorise the structure of GeoJSON - you can just look it up if you really want to know it. The important thing is that you have a broad understanding of what it is, and that you know how to use it!

We will be looking at various approaches to dealing with JSON and GeoJSON data throughout the course, but for now I have loaded it into a .js file for you, which you can simply add to your script.

Make a new Leaflet web page and call it week3.html. Now add a <script> tag to import http://geographicalinformation.science/maps/data/countries.js into your web page (think about where this should go).

If that worked, you should have the full dataset loaded into a global variable called countries.

To test if this is the case, use console.log() to print out countries and see if the data is there.

Adding GeoJSON to a Leaflet Map

Now we have our data, we can add it to our map. Before we do that, however, let’s do a bit of setup for our map.

Make the map 500px wide and 500px tall. As we are now using px and not %, you can also delete the entire selector for html and body

Update the setview arguments for the map as follows: .setView([0, 0], 1)

If those two have worked, you should now be looking at a square map that shows the entire world. If so, great work! Now we will add some data to it, which fortunately is quite simple as Leaflet was designed to work with GeoJSON from the ground up. As a result, adding your data is as simple as calling the constructor for a Leaflet geoJson() object, and then using its .addTo() function to add it to the map:

// load the geojson data, style it, set events and add to map
geojson = L.geoJson(countries, { }).addTo(map);

Add a new global variable called geojson and then add the above snippet to the end of initMap()

You should now have a map with a layer of data, something like this:


Styling GeoJSON

Right, let’s have a closer look at that geojson constructor again- it has two arguments:

countries the GeoJSON data that you want to make into a geoJson object
{} an empty object

The empty object is an example of an object literal - and it is used to load options into the geoJSON object, allowing us to do things like add events or style it. To illustrate this, let’s add some style to the countries:

// load the geojson data and style it
geojson = L.geoJson(countries, { 
	style: {
		weight: 0.5,
		color: 'white',
		fillOpacity: 1,
		fillColor: 'lightgrey',

Can you see how that works? The object literal that we are using for the options contains a single property called style, for which the value is another object literal containing four properties:

weight The thickness of the outlines
color The colour of the outlines
fillOpacity The opacity of the countries (0 = transparent, 1 = opaque)
fillColor The colour of the countries

Replace your previous geoJson() constructor with the one above and then change the style yourself

You should now have styled countries, like this:

Whilst that is all well and good, it is often useful if we can colour each country individually, based upon the values of their properties. To illustrate this, we are going to replace that static style object literal with a function that will return a style based upon the population of the country. Watch closely…

Styling GeoJSON Dynamically

Rather than simply styling GeoJSON using an object literal, we can create our own style object constructor: a function that returns a style object for each individual feature based upon its population value, meaning that each country will be coloured in individually. To do this, we simply need to specify the name of the function as the value for the style property in our geoJson constructor:

geojson = L.geoJson(countries, {
	style: style,		//set the style using a function

and then create a function to return the new style object:

 * This function styles the data (a single country)
function style(feature) {
	//return a style
	return {
		weight: 0.5,
		color: '#666',
		fillOpacity: 1,
		fillColor: getColour(feature.properties.POP_EST)	//the colour is set using a function

Add the above function to your code and set the style property of the geoJson constructor to use it (as above)

This code won’t work yet - why not?

As you can see, all of the vaues that we are returning in our style object are just as before, except one - we are calling another function called getColour() that takes in a value (population in this case) and converts it to a colour.

This function is simply taking in the population number and then using some pre-set thresholds in order to assign an appropriate colour, such that the colour reflects the number of people. Here are the levels that we will use:

0 - 13805084
13805084 - 35623680
35623680 - 68414135
68414135 - 105350020
105350020 - 157826578
157826578 - 207353391
207353391 - 1281935911
1281935911 - 1379302771

Click here to look up how hexadeimal colours work in HTML so that you understand colour values like #666 and #FFEDA0

I realise that these numbers might seem odd- but they have been calculated using Jenks’ Natural Breaks algorithm, which was developed in 1967 by a statistician called George Jenks as a way to optimise the assignment of numbers to different classes so that the values in each class are a different as possible. The colours, on the other hand, came from this colour scheme on a web tool called ColorBrewer, which is designed to help cartographers select colours that work well together on a map. Both of these are widely used by cartographers, and Color Brewer in particular is well worth remembering when your assessment comes around…

But how do we actually implement this colour scheme on our map? A conditional statement of course!

 * Return a colour based upon the given population value
function getColour(population) {
	//create a variable to hold the colour
	var colour;
	//assign a colour based upon population value
	if (population > 1281935911){
		colour = '#800026';
	} else if ( ) {
		colour = '#BD0026';
	} else if ( ) {
		colour = '#E31A1C';
	} else if ( ) {
		colour = '#FC4E2A';
	} else if ( ) {
		colour = '#FD8D3C';
	} else if ( ) {
		colour = '#FEB24C';
	} else if ( ) {
		colour = '#FED976';
	} else {
		colour = '#FFEDA0';
	//return the resulting colour
	return colour;

Oh dear - I seem to have forgotten to fill in the else if statements! Paste the above into your code and fill in the blanks between the brackets so that the function works according to the legend above

If that works, you can then add a legend in HTML like this:

<!-- Add the legend for the map -->
<div class="legend">
	<i style="background:#FFEDA0"></i> 0 - 13805,084 <br>
	<i style="background:#FED976"></i> 13,805,084 - 35,623,680 <br>
	<i style="background:#FEB24C"></i> 35,623,680 - 68,414,135 <br>
	<i style="background:#FD8D3C"></i> 68,414,135 - 105,350,020 <br>
	<i style="background:#FC4E2A"></i> 105,350,020 - 157,826,578 <br>
	<i style="background:#E31A1C"></i> 157,826,578 - 207,353,391 <br>
	<i style="background:#BD0026"></i> 207,353,391 - 1,281,935,911 <br>
	<i style="background:#800026"></i> 1,281,935,911 - 1,379,302,771 

And the corresponding styles:

/* Avoid having a newline between the two divs */
div {
/* Style the legend */
.legend { 
	font-family: Arial, sans-serif;
	font-size: small;
	padding-top: 10px;
	text-align: left; 
	line-height: 18px; 
/* Style the i tags within the legend - we use these to make the coloured squares */
.legend i { 
	width: 18px; 
	height: 18px; 
	float: left; 
	margin-right: 8px; 

Add in the legend as well (think about where you should put it)

If that went according to plan, you should be looking at something like this:

Looking good!

Events with GeoJSON

The last thing that we are going to look at this week is adding some events to that data layer in order to make it interactive. In particular, we are going to make it so that if a user hovers their mouse over a country, it will go yellow and its name will appear on a panel on the map.

First things first then, let’s add a panel to the map. The code for adding a panel looks a bit wierd, but it pretty formulaic, I would just accept that this is what it is an move on… There are two components: some JavaScript to add the new element to the map, and some CSS to dictate what it should look like:

/* Style the info box so that it looks like a Leaflet control */
.info { 
	font-family: Arial, sans-serif;
	padding: 6px 8px; 
	background: white; 
	background: rgba(255,255,255,0.8); 
	box-shadow: 0 0 15px rgba(0,0,0,0.2); 
	border-radius: 5px; 
 * Create the info box in the top right corner of the map
function addInfoBox(){
	// create a Leaflet control (generic term for anything you add to the map)
	info = L.control();

	//create the info box to update with population figures on hover
	info.onAdd = function (map) {
		this._div = L.DomUtil.create('div', 'info');
		return this._div;

	//create a function called update() that updates the contents
	info.update = function (props) {
		//if properties have been passed, then use them to fill in the info box
		if (props){
			this._div.innerHTML = '<b>' + props.NAME + '</b><br />' + parseFloat(props.POP_EST).toLocaleString('en');
		//otherwise, just set to a message
		} else {
			this._div.innerHTML = 'Hover over a country';

	// add the info window to the map

Add the CSS and JavaScript to the relevant places on your web page, and then add a function call for addinfoBox() in initMap() so that the info box gets added to the map just after it is created. (If you need some help calling the function, check the Snippets page…)

Take a look at the parseFloat(props.POP_EST).toLocaleString('en'); section of that last snippet - this is a bit of extra code that adds commas as thousand separators to your numbers - which makes them much easier to read! You will also notice that we have added a function called update() to the info object (which was created with the L.control() object constructor). This makes use of a conditional statement by checking whether or not an argument was passed to it when it was called. If an argument was passed, then it will assume that the argument is an object containing the properties of a feature that has been hovered over, so it retrieves the name (NAME) and population (POP_EST) for that object and displays it in the box. If nothing has been passed, then it simply resets the box to the default message: “Hover over a country”.

Have a careful look at the above snippet and make sure that you can follow it all. If not, ask!

Now your map should have a little box floating in the top left that says “Hover over a country”. Obviously this doesn’t do anything yet, as we have not set any events for it, but before long that will list the country name and the populayion as you hover over it - which is pretty cool!

Let’s give it a go… Firstly, we will set some event listeners for the GeoJSON layer on the map, and then we will makethe required functions. Here are the steps:

Firstly, add onEachFeature: setEvents to your L.geoJson options object literal:

// load the geojson data, style it, set events and add to map
geojson = L.geoJson(countries, {
	style: style,			//set the style using your function
	onEachFeature: setEvents,	//set the hover events using your function

onEachFeature will automatically call the function setEvents for each feature (country) in the GeoJSON dataset as it is created. You can, therefore, use this function to add the required event listeners to the necessary events associated with each country. In this case, we are interested in mouseover (when the mouse cursor goes over the country) and mouseout (when the mouse moves off the country). If we make a change to the style on mouseover and then reset that change on mouseout, we will have the effect of highlighting the country that the mouse is hovering over.

Add the following setEvents function to your code:

 * Create a function to tie the mouseover and mouseout events to re-styling the layer
function setEvents(feature, layer) {
		mouseover: ,
		mouseout: ,

Whoops, it looks like I forgot to finish that code too! Fill in the blanks so that mouseover is tied to highlightfeature and mouseout is tied to resetHighlight. Remember that these functions don’t exist yet so you won’t see anything happen on your page!

Now take a second to see how this works. Just like we used map.on() to set the listener our click event for the map last week, we are now using layer.on() to set the listeners for the mouseover and mouseout events on the dataset. This function also gives you an insight into how the [onEachFeature](https://leafletjs.com/reference-1.4.0.html#geojson-oneachfeature) option works, it calls the specified function and passes it two arguments: the first (called feature in our case) is the specific feature (coutry), and the second (called layer in our case) refers to the entire layer.

Now all that remains is to create the functions for highlightFeature and resetHighlight.

 * This function re-styles the data to yellow (for when you hover over it)
function highlightFeature(e) {
	// e refers to the event object
	// e.target is the country that was hovered over
	var layer = e.target;

	// set the style to yellow
		color: ,
		fillColor: ,

	//update the info box

 * Reset the style after the hover is over
function resetHighlight(e) {
	// e refers to the event object
	//reset the style of the country that was yellow
	geojson.resetStyle(e.target);	// e.target is the country that was hovered over
	//update the info box

Add the above functions to your code and fill in the blanks in the object literal that is being passed to setStyle() in highlightFeature() and the missing final line in resetHighlight()

If that all went to plan, then congratulations, you have created an interactive population map:


Some of the material on these pages is derived from the excellent Leaflet Tutorials and Mozilla Developers websites. Mozilla Developers by Mozilla Contributors is licensed under CC-BY-SA 2.5.