Chapter 6 Leaflet

Last updated: 2018-11-11 20:44:59

6.1 Introduction

This Chapter introduces Leaflet, a JavaScript library used to create interactive web maps. Using Leaflet, you can create a simple map using just two or three expressions lines of code, or you can build complex maps that contain hundreds of lines of code.

In this Chapter, we will learn how to initialize a Leaflet web map on our web page, and how to add several types of layers on it: a tile layer (Section 6.5.6), and simple shapes such as point markers (Section 6.6.1), lines (Section 6.6.2) and polygons (Section 6.6.3). We will also introduce map events - browser events associated with the web map (Section 6.9). In the following two Chapters we will learn some more advanced Leaflet functionality. In Chapter 7 we will learn to add more complex shapes coming from external GeoJSON files. Then, in Chapter 8, we will learn how to define symbology and interactive behaviors in our web map.

6.2 What is a web map?

We already introduced the concept of web mapping in Chapter 0.1. We mentioned that the term “web map” usually implies a map that is not simply on the web, but rather one that is powered by the web. It is usually interactive, and not always self-contained, meaning that it “pulls” content from other locations, such as tile layer servers or database APIs.

Similarly to spatial information display in GIS software, web maps are usually composed of one or more layers. Web map layers can be generally classified into two categories -

  • Background layers or basemaps, comprising collections of gridded images or vector tiles, which are usually general-purpose and not prepared specifically for our map
  • Foreground layers, which are usually vector layers (points, lines, and polygons), commonly prepared and/or fine-tuned for the specific map web where they are shown

Background layers are usually static and non-interactive, while foreground layers are usually dynamic and associated with user interaction, such as querying their attributes by clicking on a feature.

A good introduction to web mapping can be found in the Web Maps 101 presentation from Maptime Boston.

6.3 What is Leaflet?

Leaflet (Crickard III 2014) is an open-source JavaScript library for building interactive web maps. Leaflet was initially released in 2011. It is lightweight, relatively simple, and flexible. For these reasons, Leaflet is probably the most popular open-source web mapping library at the moment.

As the Leaflet home page puts it, the guiding principle behind this library is simplicity:

Leaflet doesn’t try to do everything for everyone. Instead it focuses on making the basic things work perfectly.

6.4 Alternatives to Leaflet

In this book we will exclusively use Leaflet for building web maps. However, it is important to be aware of the landscape of alternative web mapping libraries, their advantages and disadvantages. Table 6.1 lists Leaflet along with other popular JavaScript libraries for web mapping.

TABLE 6.1: Popular web mapping libraries
Library Released Type URL
Google Maps 2005 Commercial https://developers.google.com/maps/
OpenLayers 2006 Open-source https://openlayers.org/
ArcGIS API for JS 2008 Commercial https://developers.arcgis.com/javascript/
Leaflet 2011 Open-source https://leafletjs.com/
D3 2011 Open-source https://d3js.org/
Mapbox GL JS 2015 Commercial https://www.mapbox.com/mapbox-gl-js/api/

OpenLayers (Gratier, Spencer, and Hazzard 2015; Langley and Perez 2016; Farkas 2016) is an older, more mature and more richly featured open-source JavaScript library for building web maps, otherwise very similar to Leaflet in its scope. However, OpenLayers is also more complex, heavier (in terms of JavaScript file size) and more difficult to learn. Leaflet can be viewed as a lighter and more focused alternative to OpenLayers.

D3 (Murray 2017) is an open source JavaScript library for visualization in general, though it is commonly used for mapping too (Newton and Villarreal 2014). D3 is primarily used for displaying vector layers, as raster tile layers are not well supported. D3 is probably the most complex and difficult library of those mentioned here. However, it is very flexible and can be used to create truly innovative map designs. Go back to the examples from section 0.1 - some of the most impressive and innovative ones, such as Earth Weather, are created with the help of D3.

Google Maps JavaScript API (Dincer and Uraz 2013) is a proprietary web mapping library by Google. The biggest advantage of the Google Maps API is that it brings the finely crafted look and feel of the Google Maps background layer to your own web map. On the other hand, background layers other than Google’s are not supported. The Google Maps API also has advanced functionality not available elsewhere, such as integration with Street View scenes. However the library is closed-source, which means the web maps cannot be fully customized. Also, it requires a paid subscription.

ArcGIS API for JavaScript (Rubalcava 2015) is another commercial web mapping solution. The ArcGIS API is primarily designed to be used with services published using ArcGIS Online or ArcGIS Server, though general data sources can also be used. The ability of the ArcGIS API to tap into web services originating from ArcToolbox that perform geoprocessing on the server is a feature which has no good equivalent in open-source software. However, using an ArcGIS Server requires a paid (and expensive) licence. Also, the APIs are free to use for development or educational use, but require a fee for commercial use.

Mapbox GL JS is a web mapping library provided by a commercial company named Mapbox. Notably, Mapbox GL JS maps use customizable vector tile layers (Section 6.5.5.2) as background. You can use existing basemaps, or build your own using the interactive “studio” web application. Like Google Maps, Mapbox GL JS also requires a paid subscription, though the first 50,000 monthly map views are free.

See the What is a web mapping API? article in the GEOG 585 course materials for another overview of pros and cons of different web mapping libraries.

6.5 Creating a basic web map

In this section, we will learn to create a basic web map featuring a single background (tile) layer, initially zoomed-in on the Ben-Gurion University. The final result is shown on Figure 6.5.

6.5.1 Web page setup

We will start with the following HTML template, which we are familiar with from Chapter 1 -

<!DOCTYPE html>
<html>
<head>
    <title>Basic map</title>
</head>
<body>
    <!-- Our web map and content will go here -->
</body>
</html>
  • Create an empty text file named index.html and copy the above code into that file
  • Follow the steps discussed in the subsequent sections, to build your own web map from the ground up

At this stage the web page is empty. From here, we will do the following four things to add a map to our page -

  • Include the Leaflet CSS and JavaScript files
  • Add a <div> element to our page that will hold the map
  • Create a map object, which will initiate a Leaflet map inside the <div> element
  • Add the tile OpenStreetMap basemap to our map object using tileLayer

6.5.2 Including Leaflet CSS & JS

We need to include the Leaflet library on our web page before we can start using it. There are two options for doing this, just like we discussed when including the jQuery library (Section 4.4). We can either download the library files from the Leaflet website and load those local files, or we can use a hosted version of the files from a CDN. In the examples, we use the “local copy” option.

Unlike jQuery, Leaflet consists of two files: a JavaScript code file and a CSS file.

To add the CSS file, within the <head> section (after the <title>) we need to add a <link> element referencing the file. This adds the Leaflet CSS file to our web page to include the Leaflet styles. We will use a local copy -

<link rel="stylesheet" href="css/leaflet.css">

Remember that the path the the local file needs to correspond to the website directory structure (Section 5.5.2). For example, in the above <link> element we are using a relative path css/leaflet.css, meaning that the leaflet.css file is located in a sub-directory names css inside the directory where the index.html file is.

For loading the file from a CDN we could replace the leaflet.css part with the following URL -

https://unpkg.com/leaflet@1.3.3/dist/leaflet.css

After the CSS reference, we need to add a <script> element referring to the Leaflet JavaScript file. Again, we are going to use a local copy as follows -

<script src="js/leaflet.js"></script>

For loading the Leaflet JavaScript file from a CDN, replace leaflet.js with the following URL -

https://unpkg.com/leaflet@1.3.3/dist/leaflet.js

Either way, the Leaflet CSS and JavaScript files are now linked to our web page and we can begin working with the Leaflet library. Note that the local files provided in the online supplement as well as the above CDN URLs load Leaflet version 1.3.3 (Table .). In case we need to load a newer or older version, the local copies or URLs can be modified accordingly.

When working with a local copy of the Leaflet library, in addition to the JavaScript and CSS files we also need to create an images directory within the directory where our CSS is (e.g. css) and place two PNG image files there: marker-icon.png and marker-shadow.png. These files are necessary for drawing markers on top of our map (Section 6.6.1). More information on how Leaflet actually uses those PNG images will be given in Section 11.2.2.

6.5.3 Adding map <div>

Our next step is to add a <div> element, in the <body>. The <div> will be used to hold the map. As we learned in Section 1.5.11, a <div> is a generic grouping element. It is used to collect elements into the same block group so that it can be referred to in CSS or JavaScript code. The <div> intended for our map is initially empty, but needs to have an ID. We will use JavaScript to “fill” this element with the web map, later on.

<div id="map"></div>

In case we want the web map to cover the entire screen, which is what we will usually do in this book, we also need the following CSS rules -

body {
    padding: 0;
    margin: 0;
}
html, body, #map {
    height: 100%;
    width: 100%;
}

This CSS code can be added in the <style> element in the <head>. Recall that this is known as embedded CSS (Section 2.6.2).

6.5.4 Creating a map object

Now that the Leaflet library is loaded, and the <div> element which will be used to contain it is defined and styled, we can move on to actually adding the map, using JavaScript.

The Leaflet library defines a global object named L, which holds all of the Leaflet library functions and methods. This is conceptually very similar to the way that the jQuery library defines the $ global object.

Using the L.map method, our first step is to create a map object. When creating a map object there are two important arguments supplied to L.map -

  • The ID of the <div> element where the map goes in
  • Additional map options, passed as an object

In our case, since the ID of the <div> intended for our map is "map", we can initiate the map with the following expression, which goes inside the <script> in the <body> -

L.map("map");

As for map options, there are several ones that we can set. The options are passed together, as a single object, where property name refers to the option and the property value refers to the value which we want to set. The most essential options are those specifying the initially viewed map extent. For example, center and zoom can be used to specify the coordinates where the map is initially panned to, and its initial zoom level, respectively.

For example, to focus on the Ben-Gurion University we can indicate the [31.262218, 34.801472] location and set the zoom level to 17. The above expression now takes the following form -

L.map("map", {center: [31.262218, 34.801472], zoom: 17});

(You can always to refer to the documentation for the complete list of options for L.map, or any other Leaflet function.)

Note that the center option is specified in geographic coordinates (latitude and longitude) using an array of the form [lat, lon], that is in the [Y, X] rather than the [X, Y] order. This may seem unintuitive to GIS users, but the [lat, lon] ordering is actually very common in many applications that are not specifically targeted to geographers, including Leaflet and other web mapping libraries such as the Google Maps API. When working with Leaflet, you need to be constantly aware of the convention to use [lat, lon] coordinates, to avoid errors such as displaying the wrong area.

Also, keep in mind that web mapping libraries, including Leaflet, usually work with geographic coordinates (longitude, latitude) as far as the user is concerned. This means that all placement settings (such as map center), as well as layer coordinates (Section 6.6 below) are passed to Leaflet functions as geographic coordinates (latitude, longitude), i.e. the WGS84 system (EPSG:4326). The map itself, however, is displayed using coordinates in a different system, called the Web mercator projection (EPSG:3857) (Figure 6.1).

World map in the WGS84 (<code>EPSG:4326</code>) and Web mercator (<code>EPSG:3857</code>) projections

FIGURE 6.1: World map in the WGS84 (EPSG:4326) and Web mercator (EPSG:3857) projections

For example, the following [lon, lat] coordinates (EPSG:4326) for central London passed to Leaflet -

[-0.09, 51.51]

Will be internally converted to the following [X, Y] coordinates in Web mercator (EPSG:3857) before being displayed on screen -

[-10018.75, 6712008]

The user never has to deal with the Web mercator system, since it is only used internally, before drawing the final display on screen. However it is important to be aware of the issue.

As for the zoom, you may wonder what does the 17 zoom level mean. This will be explained when discussing tile layers (Section 6.5.5).

Note that the L.map function returns a map object, which has useful methods and can be passed to methods of other objects. Therefore, we usually want to save that object in a variable, such as the one named map in the following expression, so that we can refer to it when running those methods later on in our script.

Combining all of the above considerations, we should now have the following JavaScript code in the <script> element on our web page -

var map = L.map(
    "map", 
    {center: [31.262218, 34.801472], zoom: 17}
);

If you open the map at this stage, you should see that it has no contents whatsoever, just grey background with the “+” and “-” (zoom-in and zoom-out) buttons which are part of the standard map interface. Our next step is to add a background layer on the map.

6.5.5 What are tile layers?

Tile layers are a fundamental technology behind web maps. They comprise the background layer in most web maps, thus helping the viewer to locate the foreground layers in geographical space. The word tile in tile layers comes from the fact that the layer is split into individual rectangular tiles.

Tile layers come in two forms -

  • Raster tiles
  • Vector tiles

6.5.5.1 Raster tiles

The simplest and oldest tile layer type is where tiles are raster images, also known as raster tiles. With raster tiles, tile layers are usually composed of PNG images, each having the size of \(256\times 256\) pixels.

A separate collection of tiles is required for each zoom level the map can be viewed on, with increasing number of tiles needed to cover a (global) extent in higher zoom levels. Conventionally, at each sequential zoom level, all of the tiles are divided into four “new” ones (Figure 6.2). For example, for covering the world at zoom level 0, we need just one tile. When we go to zoom level 1, that individual tile is split to \(2\times 2=4\) separate tiles. When we go further to zoom level 2, each of the four tiles is also split to four, so that we already have \(4\times 4=16\) separate tiles, and so on. In general, a complete tile layer at zoom level z contains \(2^z\times 2^z=4^z\) tiles. The default maximal zoom level in a Leaflet map is 19; 274,877,906,944 tiles are needed to cover the earth at this zoom level!

OpenStreetMap tiles for global coverage at zoom levels 0, 1 and 2

FIGURE 6.2: OpenStreetMap tiles for global coverage at zoom levels 0, 1 and 2

The main rationale for tiled maps is that only the relevant content is loaded in each particular viewed extent. Namely, only those specific tiles that cover the particular extent we are looking at, in a particular zoom level, are transferred from the server. For example, just one or two dozen individual tiles (Figure 6.3) are typically needed to display a given map extent such as the one shown on Figure 6.5. This results in minimal size of transferred data, which makes tiled maps appear smooth and responsive.

The PNG images of all required tiles are sent from a tile server, which is simply a place where all tile images are stored in a particular directory structure. To locate the tile we need, we enter a URL such as http://.../Z/X/Y.png, where http://.../ is a constant prefix, Z is the zoom level, X is the column (i.e. x-axis) and Y is the row (i.e. y-axis).

For example, the following URL refers to a specific tile at zoom level 17, focused on building #72 at the Ben-Gurion University, from the OpenStreetMap tile server (Figure 6.3).

https://a.tile.openstreetmap.org/17/78206/53542.png

Note how the URL is structured. The constant prefix of the URL is https://a.tile.openstreetmap.org/, referring to the OpenStreetMap tile server. The variable parts are specific values for Z (e.g. 17), X (e.g. 78206) and Y (e.g. 53542).

Individual OpenStreetMap raster tile, from location 78206/53542, on zoom level 17 (downloaded on 2018-05-27)

FIGURE 6.3: Individual OpenStreetMap raster tile, from location 78206/53542, on zoom level 17 (downloaded on 2018-05-27)

You may recognize where this tile fits in the map view on Figure 6.5.

6.5.5.2 Vector tiles

A more recent tile layer technology is where tiles are vector layers, rather than raster images, referred to as vector tiles. Vector tiles are distinguished by the ability to rotate the map while keeping labels in place, and by smooth zooming with no strict division to discrete zoom levels. Major advantages of vector tiles are in their smaller size and flexible styling. For example, the Google Maps website shifted from raster to vector tiles in 2013.

The Leaflet library does not natively support vector tiles, though there is a plugin for that. Therefore in this book we will use raster tile layers as background.

There are other libraries specifically built around vector tile layers, such as the Google Maps API and Mapbox GL JS which we mentioned above (Section 6.4). The example-06-01.html shows a web map with a vector tile layer built with Mapbox GL JS (Figure 6.4). This is the only non-Leaflet web map code example in the book, provided for demonstration and comparison. We now go back to discussing Leaflet and raster tile layers.

FIGURE 6.4: example-06-01.html (Click to view this example on its own)

  • Open example-06-01.html in the browser
  • Try changing map perspective by dragging the mouse while pressing the Ctrl key
  • Compare this type of interaction with that of example-06-02.html
  • What is the reason for the differences?

6.5.6 Adding a tile layer

Where can we get a tile layer from? There are many tile layers prepared and provided by several organizations, available on dedicated servers (Section 6.5.7) that you can add to your maps. Most of them are based on OpenStreetMap data (Section 13.1), the most extensive free database of map data having a global coverage. The layer we add below, where the tile shown on Figure 6.3 comes from, is maintained by OpenStreetMap, and the default one shown on the https://www.openstreetmap.org/ website.

To add a tile layer to the map, we use the L.tileLayer function. This function accepts -

  • The URL of the tile server
  • An object with additional options

Note that the tile server URL includes the {z}, {x} and {y} placeholders, which are internally replaced by zoom level, column and row each time the Leaflet library loads a given tile, as discussed above (Section 6.5.5.1). The additional {s} placeholder refers to one of the available sub-domains (such as a, b, c), for making parallel requests to different servers hosting the same tile layer.

Here is the URL for loading the default OpenStreetMap tile layer -

https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png

The options object is used to specify other parameters related to the tile layer, such as the required attribution (shown in the bottom-right corner on the map), or the minZoom and maxZoom levels. (Again, for the complete list of options see the documentation)

For example, the following expression defines a tile layer using the OpenStreetMap server and sets the appropriate attribution. The .addTo method is then applied to add the tile layer to the map object, the latter referring to our web map. The .addTo method is applicable for adding any type of layer on our map, not just a tile layer, as we will see throughout this Chapter.

L.tileLayer(
    "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", 
    {attribution: "© OpenStreetMap"}
).addTo(map);

Our complete code for a basic Leaflet map now looks like this -

<!DOCTYPE html>
<html>
<head>
    <title>Basic map</title>
    <link rel="stylesheet" href="css/leaflet.css">
    <script src="js/leaflet.js"></script>
    <style>
        body {
            padding: 0;
            margin: 0;
        }
        html, body, #map {
            height: 100%;
            width: 100%;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>

var map = L.map(
    "map", 
    {center: [31.262218, 34.801472], zoom: 17}
);

L.tileLayer(
    "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", 
    {attribution: "© OpenStreetMap"}
).addTo(map);

    </script>
</body>
</html>

The resulting map is shown on Figure 6.5.

FIGURE 6.5: example-06-02.html (Click to view this example on its own)

  • Using the Chrome Developer Tools, we can actually observe the different PNG images of each tile being loaded while navigating on the map
  • Open example-06-02.html in the browser
  • Open the Chrome Developer Tools (by pressing F12)
  • Go to the Network tab of the Developer Tools
  • Pan and/or change the zoom level to view a different extent on the map
  • You should see a list of the new PNG images being transferred from the server, as new tiles are being shown on the map (Figure 6.6)
  • Double-click on any of the PNG image file names to show the image itself
Observing network traffic as new tiles are being loaded into a Leaflet map

FIGURE 6.6: Observing network traffic as new tiles are being loaded into a Leaflet map

6.5.7 Tile layer providers

As mentioned in the previous section, the default OpenStreetMap tile layer we just added on our map (Figure 6.5) is just one of many available tile layers provided by different organizations.

An interactive demonstration of the various tile layer providers can be found in the leaflet-providers web page. Once on the leaflet-providers web page, you can select a tile layer from the list in the right panel, then explore its preview in the central panel (Figure 6.7).

Interactive preview of <a href="https://leaflet-extras.github.io/leaflet-providers/preview/" target="_blank">tile layer providers</a>

FIGURE 6.7: Interactive preview of tile layer providers

Conveniently, the text box on the top of the page gives the exact L.tileLayer JavaScript expression that you can use in a <script> for loading the tile layer in a Leaflet map.

  • Browse the leaflet-providers page and choose a tile layer you like
  • Replace the L.tileLayer(...) expression in the source code of example-06-02.html (after making a local copy) with the appropriate expression for your chosen tile layer
  • Save the file and refresh the page in the browser
  • You should now see the new tile layer you chose on the map

6.6 Adding vector layers

So far we learned how to create a Leaflet map and add a background tile layer on top of it. Tile layers are generic and thus typically serve as background, to help the user in positioning the foreground layers geographical space.

The foreground is usually made of vector layers, such as points, lines and polygons, though it is possible to add manually created image layers too. To give an introduction on adding vector layers, in this section we will add markers (Section 6.6.1), lines (Section 6.6.2) and polygons (Section 6.6.3) as foreground on top of the tile layer on our map.

There are several methods for adding vector layers on a Leaflet map. In this section, we will add vector layers using methods where the layer coordinates (i.e. its geometry) are manually set by passing numeric arrays to the respective function. These methods are mostly useful for drawing simple, manually defined shapes. In Chapter 7 we will learn how to add vector layers based on GeoJSON strings, which is more useful to add pre-existing, complex layers.

6.6.1 Adding markers

There are three ways of marking a specific point on our Leaflet map -

  • Marker - A PNG image, such as Leaflet’s default blue marker (Figure 6.8)
  • Circle Marker - A circle with a fixed radius, in pixels (Figures 8.11, 12.6)
  • Circle - A circle with a fixed radius, in meters

To add a marker to our map, we use the L.marker function.

The L.marker function creates a marker object, which we can add to our map with its addTo method, similarly to what we saw with tile layers. While creating the marker object with L.marker we specify the longitude and latitude where it should be placed, using an array.

For example, the following expression adds a marker at the yard near building #72, where our class takes place -

var pnt = L.marker([31.262218, 34.801472]).addTo(map);

Again, note that the coordinates array follows the [lat, lon] format!

You may also wonder why we are assigning the marker layer to a variable, named pnt in this case, rather than just adding it to the map the way we did with the tile layer -

L.marker([31.262218, 34.801472]).addTo(map);

The answer is that the layer object contains useful methods, in addition to .addTo. We can apply those methods, later on, to make changes in the layer, such as adding popups (see Section 6.7 below).

The resulting map is shown on Figure 6.8.

FIGURE 6.8: example-06-03.html (Click to view this example on its own)

The marker is actually a PNG image, and you can replace the default blue marker with any other image as we will see in Section 11.2.2. However, we cannot otherwise easily control the size and color of the marker, since this would require loading a different PNG image. Therefore it is sometimes more appropriate to use circles or circle markers instead of markers.

With both circle markers and circles, instead of adding a PNG image, vector circles are being drawn around the specified location. You can set the size (radius) and appearance of those circles, by passing an additional object with options, such as radius, color and fillColor. The difference between circle markers and circles is in the way that the radius is set: in pixels (circle marker) or in meters (circle).

For example, the following expression adds a circle marker 0.001 degrees to the west from where we placed the ordinary marker -

L.circleMarker(
  [31.262218, 34.801472 - 0.001], 
  {radius: 50, color: "black", fillColor: "red"}
).addTo(map);
  • Try running the above expression in the console inside the above web map example
  • Zoom in and out; the circle size should remain constant, at 50 pixels on screen

Note that radius is given in pixels (e.g. 50), which means the circle marker maintains constant size on screen, irrespective of zoom level. Circles are a similar layer type, created using L.circle, where the radius is set in meters, which means the circle size maintains constant spatial extent, expanding or shrinking as we zoom in or out.

6.6.2 Adding lines

To add a line on our map, we use the L.polyline function. Since a line is composed of several points, we specify the series of point coordinates the line goes through as an array of coordinate arrays. The internal arrays, i.e. the point coordinates, are specified just like in the marker, in the [lat, lon] format.

In the following example, we are building a line that has just two points, but there could be many more points when making a more complex line. The line is drawn in the order given by the array, from the first point to the last one.

We can specify the appearance of the line, much like with the circle marker, by setting various options such as line color and weight (i.e. width, in pixels). In case we are not passing any options the default light blue 3px line will be drawn. In the following example we override the default color and weight options, setting "red" and 10, respectively. Note that the list of relevant options differs between different layer types, so once more you are referred to the documentation to check what is possible to modify for each particular layer type. For example, the radius option we used with a circle marker is irrelevant for a line.

var line = L.polyline(
  [[31.262705, 34.800514], [31.262053, 34.800782]], 
  {color: "red", weight: 10}
).addTo(map);

The resulting map, now with both a marker and a line, is shown on Figure 6.9.

FIGURE 6.9: example-06-04.html (Click to view this example on its own)

It is also possible to add a multi-part line, by passing an array of separate line segments: an array of arrays of arrays. We will not elaborate on this option here, because this type of more complex multi-part shapes are usually loaded from existing GeoJSON layers (which we learn about in Chapter 7).

6.6.3 Adding polygons

Adding a polygon is very similar to adding a line, only that we use the L.polygon function instead of L.polyline.

Like L.polyline, the L.polygon function also accepts an array of point coordinates the polygon consists of. Again, the polygon is drawn in the given order, from first node to last. Note that the array is not expected to have the last point repeating the first one, to “close” the shape (like in the GeoJSON format, see Section 7.2.2).

Like with circle markers, it is useful to define the border color and fill color of polygons, which can be done using the color and fillColor properties of the options object.

For example, the following expression adds a polygon composed of four nodes, with red border, yellow fill and 4px border width.

var pol = L.polygon(
  [
    [31.263127, 34.803668], 
    [31.262503, 34.803089], 
    [31.261733, 34.803561], 
    [31.262448, 34.804752]
  ], 
  {color: "red", fillColor: "yellow", weight: 4}
).addTo(map);

The resulting map, now with a marker, a line and a polygon, is shown on Figure 6.10.

FIGURE 6.10: example-06-05.html (Click to view this example on its own)

Similarly to what we mentioned concerning a line, it is also possible to add more complex polygons such as multi-part polygons or polygons with holes. Again, we will use the GeoJSON format for adding such complex shapes.

6.6.4 Other vector layer types

So far we mentioned most types of layers in Leaflet, listed in the first six lines of Table 6.2.

We haven’t mentioned the rectangle layer, but this is practically a specific case of a polygon layer so there is not much to say about it. You are invited to try it out on your own using L.rectangle.

All of the latter layer types, except for tile layers, are vector layers drawn according to coordinate arrays. As mentioned above, this is useful for manually drawing simple shapes, but not very practical for complex vector layers, which are usually loaded from existing external sources. When working with complex predefined vector layers, the most useful type of Leaflet layer is probably L.GeoJSON, which we learn about in Chapter 7.

Leaflet also supports adding Web Map Service (WMS) layers, which are (usually) raster images, dynamically generated by a WMS server (unlike tile layers which are pre-built).

Finally, Leaflet supports grouping layers using functions called L.layerGroup and L.featureGroup. We will use these later on, in Chapters 7, 10 and 13.

TABLE 6.2: Leaflet layer types
Layer Function
Tile layer L.tileLayer
Marker L.marker
CircleMarker L.circleMarker
Circle L.circle
Line L.polyline
Polygon L.polygon
Rectangle L.rectangle
GeoJSON L.geoJSON
Tile layer (WMS) L.tileLayer.wms
Layer group L.layerGroup
Feature group L.featureGroup

The complete list of Leaflet layer types is also given in the Leaflet documentation.

6.7 Adding popups

Popups are informative messages which can be interactively opened and closed to get more information about the various features shown on a map. This is similar to the “identify” tool in GIS software. When a viewer clicks on a vector feature which is associated with a popup, an information box is displayed. When the user clicks on the “X” (close) symbol on the top-right popup corner, on any other element on the map, or on the Esc key, the information box disappears.

Popups are added on the map by binding them to a given layer. Binding can be done using the bindPopup method that any Leaflet layer object has. The bindPopup method accepts a text string with the popup content. For example, we can add a popup to the line layer named line we created in Section 6.6.2, as follows -

line.bindPopup(
    "This is the path from <b>our department</b>" + 
    " to the <b>library</b>."
);

Note that popup content can contain HTML tags. In this case we just use the <b> tag to specify bold font (Section 1.5.5), but you can use any HTML element to add different types of content inside a popup, including links, lists, images, tables, videos, and so on.

The resulting map is given in example-06-06.html. Clicking on the line opens a popup, as shown on shown on Figure 6.11.

FIGURE 6.11: example-06-06.html (Click to view this example on its own)

See the Leaflet Quick Start Tutorial for another overview on adding basic layers and popups in Leaflet.

6.8 Adding a description

In Chapter 2 we saw an example of styling and placing map title and description (Section 2.9) boxes using custom HTML and CSS code. The Leaflet library provides its own simplified way to do the same task, using L.control. With L.control we can add custom map controls for different purposes, set with our custom HTML code.

In the following example we are using the L.control function to initiate a new map control named legend, and set its position to the bottom-left corner of the screen. The L.control function creates a new HTML element “above” the map, which can be filled with content such as buttons or inputs (i.e. controls), map legends or information boxes.

var legend = L.control({position: "bottomleft"});

In this case we want to fill the control with a map description, composed of text and an image. We use the .onAdd method of the newly create control to define its contents and behavior. In this case, we are creating a <div> with HTML code which creates the following content shown on Figure 6.12.

legend.onAdd = function(map) {
    var div = L.DomUtil.create("div", "legend");
    div.innerHTML = 
        '<p><b>Simple shapes in Leaflet</b></p><hr>' +
        '<p>This map shows an example of adding shapes ' + 
        'on a Leaflet map<p/>' +
        'The following shapes were added:<br/>' +
        '<p><ul>' +
        '<li>A marker</li>' +
        '<li>A line</li>' +
        '<li>A polygon</li>' +
        '</ul></p>' +
        'The line layer has a <b>popup</b>. ' + 
        'Click on the line to see it!<hr>' +
        'Created with the Leaflet library<br/>' +
        '<img src="images/leaflet.png">';
    return div;
};
Map description

FIGURE 6.12: Map description

The above piece of code basically creates a <div> element with id="legend", then adds internal HTML content into that element. Adding the HTML code is done with the plain JavaScript .innerHTML method which we met in Section 4.2.2. (We could do the same with jQuery, but it would not make the code much simpler in this case) Note that we are using the + operators in order to split the assigned HTML code string into multiple lines.

Finally, the control is added to the map with .addTo method, just like we did for map layers in all of the above examples.

legend.addTo(map);

The complete code for adding the map description is as follows -

var legend = L.control({position: "bottomleft"});

legend.onAdd = function(map) {
    var div = L.DomUtil.create("div", "legend");
    div.innerHTML = 
        '<p><b>Simple shapes in Leaflet</b></p><hr>' +
        '<p>This map shows an example of adding shapes ' + 
        'on a Leaflet map<p/>' +
        'The following shapes were added:<br/>' +
        '<p><ul>' +
        '<li>A marker</li>' +
        '<li>A line</li>' +
        '<li>A polygon</li>' +
        '</ul></p>' +
        'The line layer has a <b>popup</b>. ' + 
        'Click on the line to see it!<hr>' +
        'Created with the Leaflet library<br/>' +
        '<img src="images/leaflet.png">';
    return div;
};

legend.addTo(map);

We also need some CSS code to give our map description the final styling as shown on Figure 6.12 -

.legend {
    font-size: 16px;
    line-height: 24px;
    color: #333333;
    font-family: 'Open Sans', Helvetica, sans-serif;
    padding: 10px 14px;
    background-color: rgba(245,245,220,0.8) ;
    box-shadow: 0 0 15px rgba(0,0,0,0.2);
    border-radius: 5px;
    max-width: 250px;
    border: 1px solid grey;
}
.legend p {
    font-size: 16px;
    line-height: 24px;
}
.legend img {
    max-width: 200px;
    margin: auto;
    display: block;
}

Finally, we need to have the leaflet.png image file in the images directory inside the directory where the index.html HTML file is, for the last expression in our HTML code (below) to work. This adds the Leaflet library logo in the bottom of the map description (Figure 6.12).

<img src="images/leaflet.png">

The resulting map, with the map description information box in the bottom-left corner, is shown on Figure 6.13.

FIGURE 6.13: example-06-07.html (Click to view this example on its own)

6.9 Introducing map events

In Section 4.2.3 we learned how the browser fires events in response to user interactions, and that these events can be handled using JavaScript to make our web page interactive. We also mentioned the event object (Section 4.10), which can be used to trigger specific responses according to event properties. Recall the mouse position example-04-07.html (Figure 4.8) where mouse position was printed on screen thanks to the pageX and pageY properties of the event object. In Section 4.10, we also mentioned that custom event types and custom event object properties are often defined in JavaScript libraries for specific needs.

The Leaflet library, for example, defines specific event types and event properties appropriate for a web map. These specific events can be captured for the entire map object, or for specific features the map contains. In this context, a popup is an example of a built-in event listener, with a pre-defined behavior: opening on click, closing when the X button or any other layer is clicked. Similarly, we can define and customize other Leaflet map interactive behaviors.

For example, one of the most useful types of map events are click events, and the most important property of a map click event is where did the click occur. When the user clicks on the map, in addition to the usual click event properties the event object also contains the latitude and longitude of where the click was made. These are contained in the special .latlng property of the map event object. The .latlng event object property opens up the possibility of “capturing” the clicked coordinates, and doing something useful with them.

For example, in Chapter 11 we will use the clicked location to query nearby features from a database. In this section we will see a simpler example, displaying the clicked coordinates inside a popup which is opened on the clicked location itself.

To complete the task, we need to do a several things -

  • Add an empty popup object to our map
  • Write a function that, once called, sets popup location, fills the popup with content, and opens it on the map
  • Add an event listener stating that each time the map is clicked the function executes

Starting with the first part, an empty popup object can be created with the L.popup function, as follows. This is in contrast to the .bindPopup method we used earlier to bind a popup with an existing layer (Section 6.7).

var popup = L.popup();

In the function that the event listener will refer to, hereby named onMapClick (see below), we will be using the event object parameter e. As mentioned above, when referring to map click events, the event object includes the .latlng property. The .latlng property is itself an object of type LatLng, which holds the longitude and latitude of the clicked location. This is basically a JavaScript object with two properties and two numeric values, such as the following one (plus some additional methods which we don’t need to worry about at the moment).

{lat: 31.2596049045682, lng: 34.80215549468995}

A LatLng object can be created with L.latLng, and it can be used for specifying coordinates instead of a simple [lat, lon] array. For example, try running the following expression in the console of any of the above examples. This will add a marker at a location defined using a LatLng object.

L.marker(L.latLng(31.264, 34.802)).addTo(map);

Back to the event listener example. The LatLng object, representing the clicked location, will be used for two purposes -

  • Determining where should the popup be opened
  • Setting the popup contents

These are accomplished with two methods of the popup object, .setLatLng and .setContent. Finally, once popup placement and content are set, the popup is opened on the map using its .openOn method. The event listener function, hereby named onMapClick, thus takes the following form -

function onMapClick(e) {
    popup
        .setLatLng(e.latlng)
        .setContent(
            "You clicked the map at -<br><b>lon:</b> " + 
            e.latlng.lng + 
            "<br><b>lat:</b> " + 
            e.latlng.lat
        )
        .openOn(map);
}

To better understand what this function does, try running the following code in the console -

var myLocation = L.latLng(31.264, 34.802);
L.popup()
    .setLatLng(myLocation)
    .setContent(
        "You clicked the map at -<br><b>lon:</b> " + 
        myLocation.lng + 
        "<br><b>lat:</b> " + 
        myLocation.lat
    )
    .openOn(map);

This is basically the same code as the body of the mapClick function, only that instead of e.latlng we used a specific LatLng instance named myLocation. As a result of running this code section you should see a popup with the content shown on Figure 6.14, opened at the location specified by L.latLng(31.264, 34.802).

A popup displaying clicked location coordinates

FIGURE 6.14: A popup displaying clicked location coordinates

Finally, we add an event listener specifying that the onMapClick function should be executed on map click. Note that in this case the element responding to the event is the entire map, so that any click inside the map triggers the onMapClick function.

map.on("click", onMapClick);

The complete code we need to add to our basic map, to display clicked coordinates, is shown below -

var popup = L.popup();

function onMapClick(e) {
    popup
        .setLatLng(e.latlng)
        .setContent(
            "You clicked the map at -<br><b>lon:</b> " + 
            e.latlng.lng + 
            "<br><b>lat:</b> " + 
            e.latlng.lat
        )
        .openOn(map);
}

map.on("click", onMapClick);

The resulting map is shown on Figure 6.15.

FIGURE 6.15: example-06-08.html (Click to view this example on its own)

  • Try clicking on the map in the above example
  • As shown on Figure 6.15, the popup shows the e.latlng coordinates “as is”, with many irrelevant digits
  • The first six digits refer to sub-meter (~0.1 m) precision, which is more than enough for most types of spatial applications
  • Modify the onMapClick function so that the coordinates are rounded and only the first six digits of longitude and latitude are displayed inside the popup

6.10 Exercise

  • Create a Leaflet map with the location of places you want to visit, and the path you want to travel along between those places
  • The map should have -
  • A tile layer
  • Markers of the site locations
  • A Line with the travel path
  • Popups for each marker, containing site name
  • A description box, with your name and the list of ordered site locations
  • Optional: Check out the Layer Groups and Layers Control Tutorial, and try to add a layer control for showing or hiding the markers and the line layers, and for switching between two or more tile layers (Figure 6.16)
<code>solution-06.html</code>

FIGURE 6.16: solution-06.html

References

Crickard III, Paul. 2014. Leaflet. Js Essentials. Packt Publishing Ltd.

Gratier, Thomas, Paul Spencer, and Erik Hazzard. 2015. OpenLayers 3: Beginner’s Guide. Packt Publishing Ltd.

Langley, Peter J, and Antonio Santiago Perez. 2016. OpenLayers 3. X Cookbook. 2nd ed. Packt Publishing Ltd.

Farkas, Gábor. 2016. Mastering Openlayers 3. Packt Publishing Ltd.

Murray, Scott. 2017. Interactive Data Visualization for the Web: An Introduction to Designing with D3. “O’Reilly Media, Inc.”

Newton, Thomas, and Oscar Villarreal. 2014. Learning D3. Js Mapping. Packt Publishing Ltd.

Dincer, Alper, and Balkan Uraz. 2013. Google Maps Javascript Api Cookbook. Packt Publishing Ltd.

Rubalcava, Rene. 2015. ArcGIS Web Development. Manning Publications Company.