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 formap
,tilelayer
,latlng
,marker
andpopup
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 followingcondition
.- parentheses (
( )
) then identify thecondition
that is to be tested. - curly braces (
{ }
) then identify the block of code that runs if thecondition
evaluates totrue
.
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
orfalse
? 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
andPolygon
(for single features)MultiPoint
,MultiLineString
, andMultiPolygon
(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 buildingsFeatureCollection
, 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 importhttp://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 outcountries
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 and500px
tall.
Update the
setView
arguments for themap
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 ofinitMap()
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 theL.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:
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>
withid="out"
.
Put the
out
andlegend
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 theonEachFeature
// 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
andmouseout
! Update the function so that they are set toresetFeature
andhighlightFeature
(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 callede
First we will deal with highlightFeature
. This function needs to:
- Identify the feature that has fired the event (the one over which the user has moved the mouse)
- Set the style of that feature to yellow (to highlight it)
- Write the name and population of that feature to the
<div>
withid="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 calledfeature
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:
- 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. - 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 onmouseout
(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!!!