webMapWorkshop-logo-01

Using the Leaflet JavaScript Library

Prerequisite: Make a Basic Web Map


Download the Workshop Materials. This is Session 4.


From a mapper's paradigm, this exercise will build on our knowledge of JavaScript from the previous exercise and will demonstrate the fundamentals of JavaScript by distilling down our scripting tasks into two large components that are critical to web mapping applications:

  1. How you use JavaScript for data
  2. How you use it to add functionality and interaction to your web page and web map.

In the process of looking at these things, we will review some basic operations, including variables, objects, properties, functions, and events, and observe how they are implemented in Leaflet.


JavaScript Consoles

Remember that youc can use REPL.IT, an online sandbox for testing out JavaScript.

The In-browser JavaScript Console

Use CTRL+SHIFT+K (Windows)/CMD+OPTION+K (Mac) for Firefox, and CTRL+SHIFT+J (Windows)/CMD+OPTION+J (Mac) for Chrome to access the JavaScript console. In the following steps we will introduce some concepts, try them using the console, and look for how the concepts manifest in our web map code.

Drawing

In your JavaScript code, if you ever want to log something to the console, use:

console.log([object]);

// for example
console.log("Hello world");

// or
var hello = "Hello world"
console.log(hello)

1. Objects, Variables, and our Map

If you didn't above, Click here to Download the Data Package. Unzip it and put it in an easily accessible place. This contains the files needed for this tutorial. Open up map1.html. We arent going to change anything in it just yet, but observe what is going on.

This file is a basic Leaflet map, with no data added yet other than a basemap. We made this in the session last week, where we started a document, added the Leaflet mapping library, turned a div on our page into map using the library, then used objects from the Leaflet library and set the properties of those objects. The general conceptual syntax to create a map object using Leaflet, or to access a library and any other objects for that manner, is the following.

library.object(options).method;

To illustrate this using creating a map object, we use Leaflet library (L), then initialize a map on a div page element with id="map using the map class object (map), signify which HTML element the map class object will be applied to by giving the id of the element ("map"), and set options (i.e. zoom and center)

L.object( page element , { options }).method;

In Concept. Page element refers to element within our HTML.

L.map('map',{ center: [42.362432, -71.086086], zoom: 14});

Our example to illustrate. Page element refers to the div with id="map", we aren't using a method here.

Leaflet Maps are JavaScript Objects

Objects are the kings of JavaScript and almost everything you work with in JavaScript is an object. Objects are elements of JavaScript that have properties and values. To illustrate this, I will reference a nice example from W3schools, in real life, a car is an object. This car has properties like weight and color that are set to certain values, and it has methods, like start and stop.

js_object

In our map example, we are working with an object from the Leaflet library called map and then setting the properties of zoom and center. The map object was created by the developers of Leaflet, and is the main object (or class) for the Leaflet JavaScript library that can be customized and styled. Classes in the traditional sense do not exist in JavaScript, but you still hear the term class used. JavaScript classes are actually objects that contain other objects, functions, and events that act as elements that can be instantiated, like map. When we create a map, we create an instance of the map object.

All of the objects available in Leaflet are in the online Leaflet reference document.

Our Leaflet Map and JavaScript

Start your Localhost Server
Let's fire up a web map on our local machine and make some changes. To do this, serve your page on a localhost server on your machine. Open a command line (Command Prompt (Windows) or Terminal (Mac)), use cd to change the directory to the one where you placed the materials package you downloaded.

Once in that location start your basic Python SimpleHTTPServer by entering python -m SimpleHTTPServer and pressing return.

Then open a brower tab to http://localhost:8000/map1.html. This will take us to Map 1.

For more instructions on this, read the tutorial on localhost servers here.
Use map1.html in the materials package for this section.

If not open already, open map1.html in your text editor. It contains the basic map code we used in the first leaflet session (minus some data). A quick note, the goal is to focus on the JavaScript concepts. These concepts will be manifested and illustrated through our web map example.

The first item we will look at is our map object. To fully instantiate our map object in our webpage, set the the map object from the Leaflet library equal to a global variable named map. We can then access the map object properties, values, and methods by referencing the variable map throughout the rest of our script. Tell JavaScript to use the object map from the Leaflet library (L), bind it to the div page element with id='map', then set some of the map options.

The following is already in the html file, but see how it sets our map equal to a global reference variable.

var map = L.map('map',{ center: [42.362432, -71.086086], zoom: 14});

Methods

In the following block of code, we use the tileLayer object and the addTo(map) method to add an OpenStreetMap tile layer to the map object.

The following is also already in the file, but note the usage of JavaScript objects and methods to add the tile layer.

// Add Tile Layer
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);

As was shown last week, other tile layers can be loaded. I have created a DUSPviz page that contains some common basemaps, or you can use the Leaflet Providers plugin, which contains many basemaps and can be found here.

Properties and Values

In our map example, we can set various properties of our map object, these include the zoom controls and events, panning controls, and attribution options. Take a look at our map object code. Note our variable is an object that contains another object, our map!

var map = L.map('map',{ center: [42.362432, -71.086086], zoom: 14 });
The properties available for our map are found in the Leaflet document on the Map object. These can be accessed, modified, and set to customize various components and actions of the map. To display this, lets disable zoom on a double click. Add the following to your map script. One of the properties we can access is called doubleClickZoom, which is boolean. To access this, type the map object, followed by the property doubleClickZoom, and use the disable() option of the doubleClickZoom property. (disable() is a pre-built option in the Leaflet library).

// Disable double click
map.doubleClickZoom.disable();

Refresh and double click on the map. You can't zoom anymore because of the property setting we just changed.

Our Map

Basic Map - No Click Zoom (Click to view this example on its own.)

Read more about Object Properties and Values

Name one thing objects and their associated properties remind you of. Data and attributes!

2. JavaScript Objects as Datasets: GeoJSON

JavaScript objects, as we discussed in the last session, can store data. The data we are going to use with our Leaflet map is written and stored in Geographic JavaScript Object Notation, or GeoJSON.

JSON and GeoJSON

JavaScript Object Notation (JSON) is a lightweight data interchange format that is easy for humans to read and write and provides a rigid, easy to follow structure for machines to parse, manipulate, and generate. An example of a simple JSON dataset called 'employees' is below. This JSON object can be referenced as a variable in your JavaScript code (i.e. setting var directory equal to our JSON), or imported as a separate JavaScript document using a link relation. From the last session, a basic JSON:

// set employee directory dataset
var directory = {"employees":[
    {"firstName":"John", "lastName":"Doe"}, 
    {"firstName":"Anna", "lastName":"Smith"},
    {"firstName":"Peter", "lastName":"Jones"}
]}

GeoJSON is an enhanced, geographically-enabled form of JavaScript Object Notation, meaning each listing contains a latitude and longitude that we can then place on a map.

A GeoJSON Dataset

Following is a code snippet containing point locations of two coffee shops near Kendall Square, a Dunkin Donuts and a Starbucks. We want to add this dataset to our map. Copy and paste the following code snippet into the script section of your HTML page.

// set coffee shop dataset to variable coffeeShops
var coffeeShops = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "name": "Dunkin Donuts",
                "address": "1 Broadway #1, Cambridge, MA 02142",
                                "latitude": 42.362504,
                "longitude": -71.083372
            },
            "geometry": {
                "type": "Point",
                "coordinates": [-71.083372, 42.362504]
            }
        },
        {
            "type": "Feature",
            "properties": {
                "name": "Starbucks",
                "address": "6 Cambridge Center, Cambridge, MA 02142",
                                "latitude": 42.363884,
                "longitude": -71.087749
            },
            "geometry": {
                "type": "Point",
                "coordinates": [-71.087749, 42.363884]
            }
        }
    ]
};

It is a GeoJSON Feature Collection (object!) containing two features. Each feature is an object has properties that represent attributes of that data point. We are looking at coffee shops, and each of the coffee shops has four different attributes, name, address, latitude, and longitude.

Add GeoJSON to our Map

Adding a GeoJSON to our map is simple. We can use the L.geoJson object to parse the GeoJSON data and display it on the map. L.geoJson accepts an GeoJSON object, which we gave the name coffeeShops, then adds the data to a map object, which we gave the name map. Enter the following line of code in your script following your coffeeShops GeoJSON.

// add coffeeShops to map
L.geoJson(coffeeShops).addTo(map);

Save your document.

Bind Popups to our GeoJSON Features

The L.geoJson object has a number of options you can set. These options allow for styling, filtering, and attaching events and popups to the individual features of the GeoJSON. One of the options that can be specified is called onEachFeature. The onEachFeature option is a function that will be invoked on each feature when it is created. Functions are a fundamental part of JavaScript, and you will get used to writing them as you get more experience coding. A function, in the simplest form, is a block of code that is executed when something invokes it, or tells it to run. This means we can use onEachFeature to tell Leaflet to create a popup every time a feature is created.

The block of code below sets the onEachFeature option for our coffee shop GeoJSON dataset, and runs a function that binds a popup to our feature layer. Enter this into the L.geoJson object in your script.

// add coffee shop GeoJSON to map as layer
L.geoJson(coffeeShops, {
    onEachFeature: function (feature, layer) {
        layer.bindPopup(feature.properties.name);
    }
}).addTo(map);

Note the method we used to access the properties of our feature (objectName.property), our object is the feature in the GeoJSON, which onEachFeature lets us access, and the property is name.

Save and refresh.

Our Map

We just slyly got into functions, a fundamental component of JavaScript and user interactivity. Let's talk about them more.

3. Functions and Events

Functions are a fundamental part of JavaScript and are instrumental to adding advanced capabilities and including interaction to your web page and web map. A JavaScript function is a block of code that is designed to perform a particular task and is executed when it is called upon, or invoked. Ways that a function can be called upon vary, and generally come from events such as mouse clicks, page loads, the status of another event, or certain processes on your page completing. For example, you might have a function that states, in pseudo-code, when you get a mouse click, run this script. On a mouse click, add this layer.

Like almost everything else in JavaScript, functions are objects. Functions can be stored as variables, and have both properties and methods. But first, lets breakdown the components. JavaScript functions are split into key components, the function declaration, function name, function parameters that define the inputs (arguments) that are used in the function, and the code to be executed when the function is invoked.

Example Functions

1. Function Declaration

Functions are declared using the function keyword, then saved for when they are called. Syntax looks as follows.

// function declaration with two parameters
function multiply_this(a,b) {
    return a*b;
}

// invocation
multiply_this(x,y)
20 // return value

2. Function Expressions

Functions can be held in variables. These functions are anonymous functions, and are not required to have a name.

// function expression
// this is an anonymous function (an unnamed function)
// functions stored in variables do not need a name, but rather are called using the variable name
var multiply_that = function(a,b){
    return a*b;
}

mutiply_that(x,y) // run function
20 // return value

Anonymous functions, or function expressions are where you see a function without a name or declaration.

The result returned by each function will 20. The value object can be used subsequently until it is overwritten or changed to a difference value.

Notice how we used function expression and function declaration in the Create a Basic Web Map session to create a function that gives us the latitude and longitude of the location where we click on the map.

// create an empty popup element
var popup = L.popup();

// write a function that will populate the popup element using methods from the popup object
function onMapClick(e) {
    popup
        .setLatLng(e.latlng)
        .setContent("You clicked the map at " + e.latlng.toString())
        .openOn(map);
}

// on click, run function to populate popup and open it on the map
map.on('click', onMapClick);

View the click event example from Leaflet Part 1 here.

You can also use a constructor to create an instance of a function. The new constructor creates a new 'instance' of a function that sits within a variable. This is similar to taking the map object in Leaflet and creating a new instance of it by changing the map parameters.

Add a Layer Toggling Function to your Map

In our example that we are working in this week, you will notice I included a div HTML element with an ID of id="controls", right beneath our map div. You also likely noticed that it does not do anything, yet. Lets 'activate' this button by telling it to listen for a click, and invoke a function that has a snippet of code that adds the GeoJSON to our map.

To start, let's change the code we used to add the GeoJSON a bit. Because we do not want to add the GeoJSON to our map element immediately, remove the addTo(map) from the end of our L.geoJson object. Next, set the whole L.geoJson object (note how synonymous objects and functions are when you look at the properties), to a global variable. Call the variable coffeeShopPoints. Your L.geoJson code should now look like this, with the GeoJSON stored in a variable called coffeeShopPoints. We are going to keep our popups enabled, so we will still include the bindPopup function on each feature as it is added.

// set coffeeShopPoints to a our coffeeShop GeoJSON
var coffeeShopPoints = L.geoJson(coffeeShops, {
    onEachFeature: function (feature, layer) {
        layer.bindPopup(feature.properties.name);
    }
});

Now we have a variable object operating in the DOM when we load the page called coffeeShopPoints. This object is not added to our map, so it will not be visible, but we can work with it in the DOM.

We know how to add the GeoJSON to the map when we want (we use addTo(map)!). This can be a snippet of code that sits in a function that is called. To do this, add a button to our map, give it an id of addButton, and make the text say "Show All Coffee Shops". In the controls div, add the button element.

<div id="controls" style="margin: 15px;">
    <button id="addButton">Show All Coffee Shops</button>
</div>

Event Listeners

We now have a button with the id of addButton as an element on our page. We can use JavaScript to access this button element, create a listener that will look for a click. Back in the script element of our page below the coffeeShopPoints variable, enter the following line of code.

// Create event listener for the Add Coffee Shops Button
document.getElementById("addButton").addEventListener("click", addCoffeeShops);

One of the most common methods to access an HTML element is to use the id of the element. Using getElementById is a method that will allow our JavaScript to access this element, then allow us to use additional methods to perform actions on or modify this specific element. We want to access the element with id="addButton". Next we add an event handler to our element that will listen for a click on addButton. This is called an Event Listener. Event listeners can listen for clicks, mouseovers, mouseout, resize, and other events, more on this on the W3 schools site. Tell our button element to listen for a "click". The last parameter is to provide a function that will run when the click occurs, call it addCoffeeShops.

More on Event Listeners

We just referred to a function (addCoffeeShops) that doesn't exist. We want this function to do one simple thing, add our GeoJSON with the two coffee shops to our map. Create this function. It will contain one simple line of code, using the addTo() method of the GeoJSON object. The addTo() method is from LayerGroup, L.geoJson is built to include all properties from LayerGroup, so we can use it with L.geoJson. Identify our GeoJSON using the variable we gave it, coffeeShopPoints.

coffeeShopPoints.addTo(map);

Write up our function, the syntax will look like the following:

// add coffee shops function
function addCoffeeShops() {
    coffeeShopPoints.addTo(map);
};

The whole block of code to create the variable with the GeoJSON element, add an event listener that listens for a click, and the creates a function to add the layer, for those counting, follows.

// set coffeeShopPoints to a our coffeeShop GeoJSON
var coffeeShopPoints = L.geoJson(coffeeShops, {
    onEachFeature: function (feature, layer) {
        layer.bindPopup(feature.properties.name);
    }
});

// Create event listener for the Add Coffee Shops Button
document.getElementById("addButton").addEventListener("click", addCoffeeShops);

// add coffee shops function
function addCoffeeShops() {
    coffeeShopPoints.addTo(map);
};

Save and refresh your document.

Our Map

Hit Show Coffee Shops to add our coffee shop layer to the map.

Challenge

How would you create another function and button to remove a layer from the map? Hint: use map.removeLayer( element id ).

Leaflet has some built in toggling capabilities called layer controls, these can be nice to use but are harder to customize. The control layers capability allows you to add multiple layers to your map. Explore the documentation on layer controls here.

Leaflet Layer Control

We have two buttons now, what if we want to toggle using only one button? This is a great opportunity to introduce conditional statements.

4. Conditionals

Conditional statements are blocks of code that check for a condition. If the condition is true, a certain block of code will run, if the condition is not true, another block of code will run instead. There are a couple types of conditional statements, read more here. In this tutorial, we are going to look at If... Else statements. If... Else statements are very useful, for example, consider a light bulb. To tell a light switch what to do, we need to check the current state of the bulb. If the light bulb is on (i.e. light bulb on is equal to true), we can turn it off, changing light bulb on is equal to false. If the light bulb is off, it will return that the light bulb value is off (false), and we can change the status to on (true).

Create a Toggle Button

Add a third button, give it an id of toggleButton, and have it say Toggle Coffee Shops. Enter the following line of code to create the button.

<div id="controls" style="margin: 15px;">
    <button id="addButton">Show All Coffee Shops</button>
    <button id="removeButton">Hide All Coffee Shops</button>
    <button id="toggleButton">Toggle All Coffee Shops</button>
</div>

Use getElementById again to apply an event listener waiting for a click to our new toggle button. Set the id to be toggleButton, and we will create a new function called toggleCoffeeShops that will be called upon when the button is clicked.

document.getElementById("toggleButton").addEventListener("click", toggleCoffeeShops);

Create a function called toggleCoffeeShops that will contain blocks of code that turn the GeoJSON layer off and on. Within that function, enter the following If... Then statement. Because we already made functions that add the coffee shops and remove the coffee shops, we can reuse these in our conditional statement. Be careful to properly use semi-colons and brackets, the computer will not like it if there are characters missing. We are simply using the blocks of code in the functions above to turn on and off the layer, placing them in a function, check the status of the GeoJSON element, and changing the status based on the return.

The status we are checking is the map element hasLayer method. This method returns true if a layer (which ours is a GeoJSON held in the variable coffeeShopPoints) is currently added to the map, or false if it is not added to the map. We then either remove or add the layer based on the condition.

// Toggle Coffee Shops
function toggleCoffeeShops(){
    if(map.hasLayer(coffeeShopPoints)){
        removeCoffeeShops();
    } else {
        addCoffeeShops();
    }
};

Click save and continue to check out our new toggle button and witness the power of an if... else statement.

Our Map

To use multiple conditions in an If Else statement, you would use an operator, such as AND, OR, or NOT. More reading on comparisons and operators can be found on the W3Schools documentation.

Different than a conditional, but related, are loops. We aren't using a loop right now, but this would run a task until a certain condition is met. Read more on loops here.

5. Filtering

Let's look at filtering. Filtering will add to the map only objects that meet certain criteria, which for our example, could be only objects that have the name "Starbucks". The GeoJSON object contains an option for filtering, much like the onEachFeature option used to add popups to our features. We can access it using the syntax of filter in the L.geoJson options. More on the filter option. I'm going to throw some bigger chunks of code at you, because hopefully you are getting the hang of this.

Add a Filtered Subset: Option 1 - Create additional JavaScript objects

One option, for sake of illustration, is to create variables that contain subsets of our coffeeShop GeoJSON. Use the L.geoJson object and the filter option. For example, say we want to add only the GeoJSON features with the name property of Starbucks to our map. Keep the popups, and separate our filter option from onEachFeature using a comma. Enter the following block of code at the end of our script tag.

// Example of Adding a Filtered GeoJSON to our Map
// Filter GeoJSON   
var starbucksPoints = L.geoJson(coffeeShops, {
    onEachFeature: function (feature, layer) {
        layer.bindPopup(feature.properties.name);
    }, filter: function (feature, layer) {
        return feature.properties.name == "Starbucks";
    }
});

// Add the filtered GeoJSON element to our Map
starbucksPoints.addTo(map)

Note how we used the addTo method of the GeoJSON object we named starbucksPoints to add the data to the map. We could then set up conditional statements that check and see if a layer is on the map, if it is remove it, then add the starbucksLayer. This might not be the best option if we want to do alot of toggling, however, because if we end up with alot of objects it can be challenging to manage them all.

Add a Filtered Subset: Option 2 - Modify the existing JavaScript object

In this circumstance, we already have a variable declared as a GeoJSON object in our map called coffeeShopPoints. In order to keep things simple, remove the tileLayer created from coffeeShopPoints from the map, set the variable to a different filtered dataset, and then add coffeeShopPoints back into the map.

To demonstrate this, create two new buttons that when clicked will toggle a filtered subset (i.e. Dunkin Donuts, or Starbucks), by changing the coffeeShopPoints variable to the proper subset of data.

1. Add the Buttons to the controls div

In controls, add a button for Starbucks with id="filterSBux", and a button for Dunkin Donuts with id="filterDD". We will refer to those IDs when create the Event Listener.

<div id="controls">
    <button id="addButton">Show All Coffee Shops</button>
    <button id="removeButton">Hide All Coffee Shops</button>
    <button id="toggleButton">Toggle All Coffee Shops</button>
    <button id="filterSBux">Starbucks</button>
    <button id="filterDD">Dunkin Donuts</button>
</div>

2. Activate the filterSBux button and update the coffeeShopPoints layer

At the bottom of your script, start by creating an Event Listener that will listen for a click on the filterSBux button and when it gets a click, will run a function that updates the coffeeShopPoints variable.

Then write up a function containing the code that will 1) remove the coffeeShopPoints layer using removeCoffeeShops(), put this in an if statement that will only remove the coffee shops if they are already on the map, 2) set the coffeeShopPoints variable to a GeoJSON with a subset of data that has the name property of "Starbucks", 3) then add it to the map. Take a look at this in the following block of code.

// Event Listener for a click on the Starbucks button
document.getElementById("filterSBux").addEventListener("click", filterSBux);

// Function to update coffeeShopPoints to filtered subset of data showing Starbucks
function filterSBux(){
    if(map.hasLayer(coffeeShopPoints)){
    removeCoffeeShops();
    };
    coffeeShopPoints = L.geoJson(coffeeShops,{
        onEachFeature: function (feature, layer) {
            layer.bindPopup(feature.properties.name);
        }, filter: function (feature, layer) {
            return feature.properties.name == "Starbucks";
        }
    }).addTo(map);
}

Note that this example added filter as an option, just like the onEachFeature option we used for out popups. Save and refresh.

Enter the following block to perform the same task on the Dunkin Donuts button.

// Event Listener for a click on the Dunkin Donuts button
document.getElementById("filterDD").addEventListener("click", filterDD);

// Function to update coffeeShopPoints to filtered subset of data showing Dunkin Donuts
function filterDD(){
    if(map.hasLayer(coffeeShopPoints)){
    removeCoffeeShops();
    };
    coffeeShopPoints = L.geoJson(coffeeShops,{
        onEachFeature: function (feature, layer) {
            layer.bindPopup(feature.properties.name);
        }, filter: function (feature, layer) {
            return feature.properties.name == "Dunkin Donuts";
        }
    }).addTo(map);
}

Save and refresh. Try out your map. Cool huh? We have created a nice filtering technique we can use to show subsets of our data. Try adding more data and changing criteria.

Our Map

You might notice, after clicking, we have a small problem with this map. If coffeeShopPoints has been set to a filtered subset, clicking the Show All Coffee Shops button will only show the filtered subset, because it is still equal to the subset. To fix this, we need to adjust our initial coffeeShopPoints variable as a global variable with an initial value of null, and change our addCoffeeShops() function to make it add the whole GeoJSON.

Find the initial var coffeeShopPoints declaration:

var coffeeShopPoints = L.geoJson(coffeeShops, {
    onEachFeature: function (feature, layer) {
        layer.bindPopup(feature.properties.name);
    }
});

Change it to be null:

var coffeeShopPoints = null;

Find the addCoffeeShops() function:

function addCoffeeShops() {
    coffeeShopPoints.addTo(map);
};

Change it to set coffeeShopPoints equal to a GeoJSON object that is added to the map. Also, add a if statement to remove the coffee shops if they already exist. We don't want duplicate layers!:

function addCoffeeShops() {
    if(map.hasLayer(coffeeShopPoints)){
    removeCoffeeShops();
    };
    coffeeShopPoints = L.geoJson(coffeeShops, {
        onEachFeature: function (feature, layer) {
            layer.bindPopup(feature.properties.name);
        }
    }).addTo(map);
};

This make it so every time addCoffeeShops() is called, it will all of the coffee shops from the GeoJSON to the map. Save and refresh. Our map, when working, will look like the following example.

6. Loading External Data and AJAX

Open map2.html in the map package for this section
We are done with map1.html, use it as an example for your future projects!

Note: We are done with Example Map 1 (map1.html), open up Example Map 2 (map2.html)!

The example we have been working through is fully self contained. Because the coffee shop data set is embedded into our HTML/JS code, there are no requests for data made to other locations on the internet or other locations on our server. Often it is unreasonable to fully embed all elements within a single document and you have to request data from a different location. These outside requests are called HTTP requests and require using asynchronous techniques to load our data. JavaScript is synchronous is it loads everything initially from top to bottom. It is asynchronous if certain parts of the code execute at different times. To accomplish this, we use something called asynchronous JavaScript and XML, or AJAX, techniques. This allows our web application to send and receive data from a server asynchonously, or in the background, while the rest of our map operations can proceed normally.

Asynchronous Requests and AJAX

Asynchronous HTTP requests are required when you want data from an external location to be loaded into your file when it is called. For example, if you have a dataset on a different server and want to seamlessly load it into your web map without having to reload your web map, you would make an HTTP request to get that information. AJAX is a technique that allows this to happen, and you map can proceed as if nothing unusual happened. There is a nice AJAX Leaflet plugin, created by Calvin Metcalf, that works with AJAX data requests, but for this exercise, since we are introducing JavaScript, lets show another method that can be just as easy, using jQuery.

Using JQuery for an AJAX JSON request

A JSON file is actually just a plain text document, formatted in a specific manner. Writing up an AJAX request in plain JavaScript can be a bit complicated. In the previous week, we introduced briefly the JQuery library. JQuery is a super common library that aims to simplify commonly used JavaScript methods, such as Event Listeners for clicks and mouseovers, and external AJAX requests. JQuery makes it easy to manipulate a web page by finding elements on the page, setting their styles and properties, handling interaction events, and more. We want to use the method called getJSON() to load the GeoJSON into our map.

Visit the jQuery homepage to checkout the documentation.

1. The jQuery Library and our Basic Map

Above our custom scripts tag in the HTML document, we add the jQuery library to our file. This is added in map2.html, along with some basic custom scripting to add our map.

<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
<script>
    // Web map code goes here
    var map = L.map('map',{ center: [42.362432, -71.086086], zoom: 14 });
    // Tile Layer is added to our basemap here
    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap' }).addTo(map);
</script>

Save and refresh our simple map. Find it at http://localhost:8000/map2.html.

2. Use JQuery to add our external dataset

The following is a two step process, very similar to what we did above, but instead of adding a GeoJSON that is coded directly into our document, we are going to add one that sits somewhere else on our server. We need to make an HTTP request from within our code to do this, which is made easier in jQuery. In the materials, there is a data folder that contains a GeoJSON dataset of coffee shops in Cambridge called coffee_shops.geojson.

a. Create a global variable set to null that can hold our data

Enter the following after your L.tileLayer code in your custom script. Set coffeeShopPoints as a global variable with an initial value of null. This global variable can be used throughout our document, and we can change its value when we need to.

// Null variable that will hold our data
var coffeeShopPoints = null;

b. Use JQuery getJSON method to load an external GeoJSON

In the first week, we used the jQuery getJSON method to load some points. Use this again to load our coffee cafes dataset from a different location on our server, the data folder. The library object signifier for jQuery is $. To get to the getJSON method, use $.getJSON. getJSON accepts two parameters, one is the data source location, and the second is a function that will execute when the source is found and loaded. Copy and paste the following block of code after your coffeeShopPoints variable.

// load GeoJSON from an external file
$.getJSON("data/coffee_cafes.geojson",function(data){
    // set coffeeShops to GeoJSON, add GeoJSON layer to the map once the file is loaded
    coffeeShopPoints = L.geoJson(data,{
        onEachFeature: function (feature, layer) {
            layer.bindPopup(feature.properties.name);
        }
    }).addTo(map);
});

Save and refresh. Our map now has the coffee cafes dataset that was located in our data folder loaded in as a layer.

Loading External File of Coffee Shops (Click to view this example on its own.)

c. Use $.click() to activate the buttons

jQuery has Event Listener methods that make listening for events such as clicks and mouseovers much easier. The jQuery click() method is one of them, providing an an easy syntax to listen for a click and then perform an action. The code is structure like the following. Remember $ refers to the jQuery library.

$(page element identifier).method(parameters)

Activate the Add all Coffee Shops Button

Use the following to access the button that has id="addButton" and tell it what to do when it is clicked. You can the write a sequence of code within the brackets that is executed when the button is clicked.

$( "#addButton" ).click( (insert function here that is executed on click) );

For the Add Button, in case the layer already exists in the map in some form, we don't want to add it twice. If we use removeLayer, we can remove coffeeShopPoints if it exists.

map.removeLayer(coffeeShopPoints);

Then we want to get the GeoJSON again, and set it equal to coffeeShopPoints. This adds a fresh copy of the GeoJSON to coffeeShopPoints (a global variable), and adds it to the map.

$.getJSON("data/coffee_cafes.geojson",function(data){
    // add GeoJSON layer to the map once the file is loaded
    coffeeShopPoints = L.geoJson(data,{
        onEachFeature: function (feature, layer) {
            layer.bindPopup(feature.properties.name);
        }
    }).addTo(map);
});

The code to activate the #addButton element will look like the following. This can be added at the end of our script, making sure it is between the script tags, after all variables are declared.

// Use $( "elementID") and the jQuery click listener method to add all of the data layers
$( "#addButton" ).click(function() {
    map.removeLayer(coffeeShopPoints);
    $.getJSON("data/coffee_cafes.geojson",function(data){
        // add GeoJSON layer to the map once the file is loaded
        coffeeShopPoints = L.geoJson(data,{
            onEachFeature: function (feature, layer) {
                layer.bindPopup(feature.properties.name);
            }
        }).addTo(map);
    });
});

Activate Remove all Coffee Shops and Filter Starbucks

The following block of code activates the #removeButton, and #filterSBux elements on the page and can be added to the end of our script. Enter the following.

// Use $( "elementID") and the jQuery click listener method to remove all of the data layers
$( "#removeButton" ).click(function() {
    map.removeLayer(coffeeShopPoints);
});

// Use $( "elementID") and the jQuery click listener method to create a filter
$( "#filterSBux" ).click(function() {
    map.removeLayer(coffeeShopPoints);
    $.getJSON("data/coffee_cafes.geojson",function(data){
        // add GeoJSON layer to the map once the file is loaded
        coffeeShopPoints = L.geoJson(data,{
            onEachFeature: function (feature, layer) {
                layer.bindPopup(feature.properties.name);
            }, filter: function (feature, layer) {
                return feature.properties.name == "Starbucks";
            }
        }).addTo(map);
    });
});

If you read closely, we are using a very similar method to above, just with jQuery instead of vanilla JavaScript. Save and refresh your page to see your map.

Our map. Think you can activate the two unused buttons (Toggle all Coffee Shops and filter Dunkin Donuts)?

Filtering External File of Coffee Shops (Click to view this example on its own.)

Challenge: activate the other two buttons using jQuery and our external dataset. Give it a try!

Concluding Remarks

JavaScript is an extremely useful language for adding functionality to websites and web maps. Read more on the documentation at the following sites.

To check your answers, here is our final map, using an external dataset and with all of the buttons activated. Happy coding!

Coffee Shops in Cambridge - Final Working Example (Click to view this example on its own.)

Video of Intro to JavaScript, Spring 2016
Video of Intro to JavaScript (cont.), Spring 2016


Return to DUSPVIZ tutorials page