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 variables defined inside a function cannot be accessed from anywhere outside the function. This is because the variable only exists 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
const 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: const 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
	let 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
defineNumber();

//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:

let 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() and addMarker()).

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

Last week, I referred to JavaScript objects a few times without really explaining what they are. 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
proj4 Representing a transformation algorithm to move between coordinate reference systems
console Giving you access to the browser console (e.g. to print a message using console.log())

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:

{
  name: value,
  name: value,
  name: value
}

The value could be a variable, a function, another object , or a value (as a Number, String, Boolean or similar). Here is what our cup example from the above would look like:

const 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:

const 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(L.latLng(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 have been written separately like this:

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

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

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

Conditional Statements in JavaScript

An important type of JavaScript statement that we did not look at last week is a conditional statement, which is a fundamental part of JavaScript (and almost all programmoing languages) 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 an example of an if statement:

if (condition) {
  // ...then do something...
  // code in here will only run if the condition evaluates to true
} 

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.

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

Something that you must understand before starting to use if statements is that there are several values that JavaScript considers 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

Consider the below five statements, for each one would they be true or false? If you are not sure, then ask Jonny or one of the GTAs

//one
const test = "";
if (test)	// true or false...?
  
//two
const test = 1;
if (test)	// true or false...?
  
//three
const test = "tiger!";
if (test)	// true or false...?
  
//four
const test = false;
if (test)	// true or false...?
  
//five
const test = "false";
if (test)	// true or false...?

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) {
	// then...
	
} else if (condition2) {
	// then...
	
} else if (condition3) {
	// then...
	
} else {
	// otherwise...
}

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:

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

Imagine that the language variable is set to "English".

in this case, 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"?

This is the end of Part 1 so stop here as there is more lecture material to go through before we move on to Part 2


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! Here is a reminder of what a JavaScript / JSON object looks like:

{
  name: value,
  name: value,
  name: value
}

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? Notice how this object has three elements in it:

  • type: which contains a String

  • geometry, which contains another object (which in turn has two elements)
  • properties, whic contains another object (which has one element)

Remember, you don’t need to memorise this stuff, simply understand it!

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 ] ] ] ] 
		} 
	},

Remember: 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 understand what it is, and (perhaps more importantly) 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 from this template 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.

Update the setView arguments for the map as follows: .setView(L.latLng(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, which is designed to interpret geojson data, style it, add events to it, and then draw it as a layer onto the map using its .addTo() function:

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

See above how we are calling the constructor for the L.geojson layer and passing two arguments to it: the countries variable containong the geojson data itself (which you get from the countries.js script that you imported earlier), and an empty object ({ }), which is where we will put a style later on.

Add a new global variable called countriesLayer (remembering that you are going to edite the value later) 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:

Lovely!

Styling GeoJSON

Right, let’s have a closer look at that L.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 (as in, we are literally writing the object out, rather than using a constructor)- and it is used to load options into the L.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
countriesLayer = L.geoJson(countries, { 
	style: {
		weight: 0.5,
		color: 'white',
		fillOpacity: 1,
		fillColor: 'green',
	}
}).addTo(map);

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, 0.5 = 50% opacity)
fillColor The colour of the countries

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

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 style object literal with a function that will return a style object based upon the population of the country.

Here goes…

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: styleGenerator,		//set the style using a function
}).addTo(map);

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

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

Add the above function to your code and set the style property of the L.geoJson constructor (as above) in order to to use it to style your map

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

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 (hint hint…).

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

Pay attention to how we’re using the colour variable here - we declare it at the top of the function using let (so that we can update it later) and then we set it to the required value depending on the result of the if statement:

/**
 * Return a colour based upon the given population value
 */
function getColour(population) {
	
	// create a variable to hold the colour
	let 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 properly! Paste the above function into your code and fill in the blanks between the brackets so that the function works according to the legend above

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

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 
</div>

And the corresponding styles:

/* Set the font for the whole thing */
body {
  font-family: Arial, sans-serif;
}

/* Avoid having a newline between the two divs */
div {
  display:inline-block;
}

/* Style the legend */
.legend {
  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 in a <div> next to the map.

First things first then, let’s add a <div> to the page into which we can output the name and population of a country as the user hovers over it. To do this, we are going to:

add a <div> with id="out".

Put the out and legend divisions inside another <div> (just to make them easier to position)

If you did that correctly, the first part of your <body> should look like this:

<!-- This is where the map will go -->
<div id='map'></div>

<!-- store the output div and the legend in a single div to help control the layout -->
<div>

  <!-- This is where the output will go -->
  <div id='out'></div><br>

  <!-- Add the legend for the map -->
  <div class="legend">
    <i style="background:#FFEDA0"></i> 0 - 13,805,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
  </div>
</div>

Now we have somewhere to write our result to, we can set up the events to actually update the web page based upon what country the user hovers over. We control the “hover” interaction using two events:

Event Name Event Description
mouseover Fired when the mouse enters (goes over) a feature (country in this case)
mouseout Fired when the mouse exits (goes out of) a feature (country in this case)

We therefore want to set event listeners for each individual feature in the GeoJSON layer. Here is how:

We can set the event listeners for the GeoJSON layer on the map by setting the onEachFeature property in the L.geoJson options object literal. This is used to indicate a function that should be called for every single feature (country in this case) that is added to the map.

Update your L.geoJson options object literal to include the onEachFeature

// load the geojson data, style it, set events and add to map
countriesLayer = L.geoJson(countries, {
	style: style,							//set the style using a function
	onEachFeature: setEvents,	//set the events using a function
}).addTo(map);

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.

As we know, we are interested in mouseover (when the mouse cursor goes over the country) and mouseout (when the mouse moves off the country) events in this case. 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. To achieve this, we need to create oursetEvents function.

Create a new function called setEvents in the global scope in your code, as per the below code snippet:

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

Whoops, I have forgotten to set the values for mouseover and mouseout! Update the function so that they are set to resetFeature and highlightFeature (NB: you’ll need to decide which way around they go based upon your understanding of the process…)

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 option works, it calls the specified function and passes it two arguments: the first (called feature in our case) is the specific feature (country), and the second (called layer in our case) refers to the entire layer.

Why do you think that we are adding this listener to each individual feature and not simply to the layer? (Hint: Think about the structure of the GeoJSON data…). If you are not sure, ask Jonny or one of the GTAs

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

Create two functions in the global scope called highlightFeature andresetFeature. Each of them should take a single argument called e

First we will deal with highlightFeature. This function needs to:

  1. Identify the feature that has fired the event (the one over which the user has moved the mouse)
  2. Set the style of that feature to yellow (to highlight it)
  3. Write the name and population of that feature to the <div> with id="out"

Step 1 is simple, because the event object (e, remember those from last week?) that is passed to the listener contains the name of the feature that fired it:

// e.target is the country that was hovered over
const feature = e.target;

Step 2 is also easy, we set it using the setStyle function of the feature object that we have stored in feature and passing it a n object literal describing the new style:

// set the style to yellow
feature.setStyle({
  color: 'yellow',
  fillColor: 'yellow',
});

Finally, Step 3 requires the use of document.getElementById(). document is an object that represets the entire web page. the getElementById() function is basically the JavaScript equivalent of an ID Selector in CSS - it allows us to access the element that has that ID and do stuff to it (in this case, we want to put some HTML content into it containing the name and population of our country).

To put content into our element, we simply set the value of its innerHTML property, which allows us to insert some HTML into that element (this is, incidentally, exactly what Leaflet is doing when it adds the map into our <div> with id="map"). We can therefore use the + operator to concatenate (glue together) a String of HTML that will magically appear in that previously empty </div>. This String will comprise the name of the country (accessible from our feature object as feature.feature.properties.NAME) and the population of the country (accessible from our feature object as feature.feature.properties.POP_EST)).

//update the info box
document.getElementById('out').innerHTML =
  feature.feature.properties.NAME +
  "</b><br>Population: " +
  feature.feature.properties.POP_EST;

Use the previous three snippets to populate your highlightFeature function.

Now it’s time to setup the mouseout listener so that it resets the style of the feature that we made yellow in mouseover. Fortunately, this is quite simple as it isbuilt into the L.geoJson object that we have stored in the geojson variable:

//reset the style of the country that was yellow
geojson.resetStyle(feature);

Add some JavaScript to resetFeature in order to store the country that needs resetting into a variable called feature

Use the above snippet to complete resetFeature

Test you map, does it work?

If so, you should have an interactive population map almost like this:

There is just one or two details that we can add to make it a little better…

Firstly: The population numbers are a little large to be easily readable, so we should format them to make them easier to read by inserting commas as thousand separators like in my example above. To do this you need to:

  1. take the value for population and convert it to a Number using the built in JavaScript function parseFloat() . To do this, you simply need to pass the value to this function as an argument.
  2. Now, we want to chain the .toLocaleString('en') function on to the end of it that will convert it back to a String, including the thousand separator formatting. This function is designed to format numbers according to the expectations of a different language or country. Note that you pass 'en-GB' as an argument to this function - that simply is the instruction to tell the function what kind of formatting to apply (in this case, the way that numbers are formatted in the UK).

See if you can complete these two steps to get the thousand separators to appear. if you get really stuck, just ask! (Don’t be put off - this is pretty tricky!)

And secondly…

See if you can make it so that the population value in <div id="out"> disappears on mouseout (rather than remaining until you hover over another country as it does at the moment). As ever - if you get stuck, just ask!

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

Finished!

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.