Leaflet

Neil HaddleyFebruary 21, 2022

An open-source JavaScript libraryfor mobile-friendly interactive maps

Leaflet is an open-source Geographic Information System (GIS) JavaScript library for mobile-friendly interactive maps.

Leaflet has all the mapping features most developers need.

Quick start

The Leaflet web site provides a quick start tutorial.

To follow the tutorial I created an index.html page using Visual Studio Code.

emmet

emmet

template html page

template html page

Leaflet code

I added the leaflet css styles and javascript to create an interactive map.

The letter L is used to access Leaflet.

Here I replace the div element with id 'map' with the Leaflet map.

var map = L.map('map').setView([51.505, -0.09], 13);

Here I add the arcgisonline.com tiles:

L.tileLayer('//services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg').addTo(map);

index.html from localhost

index.html from localhost

Deploying index.html page to Azure Storage

Deploying index.html page to Azure Storage

Creating a new Storage Account

Creating a new Storage Account

Providing a unique name

Providing a unique name

Storage account is created

Storage account is created

Browse to web site button

Browse to web site button

index.html from azure to laptop

index.html from azure to laptop

index.html from azure to iPhone (safari)

index.html from azure to iPhone (safari)

Tile layer

Leaflet has to determine which tiles are needed to render the map.

The tiles are downloaded from a tile server (arcgisonline.com in this case).

network requests

network requests

A single tile from arcgisonline.com

A single tile from arcgisonline.com

Calculating a tile url

To determine which tile is needed to display a given location leaflet needs to convert from a given latitude and longitude to a given tile url.

The calculations are described here:

https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers

Example code:

display a given location

display a given location

To display a given area

To determine which tiles need to be downloaded to display a given area at a given zoom level leaflet needs to convert from a pair of latitude and longitude co-ordinates to a set of tile urls.

Example code:

var zoom = 9;

var top_tile = lat2tile(north_edge, zoom); // eg.lat2tile(34.422, 9);

var left_tile = lon2tile(west_edge, zoom);

var bottom_tile = lat2tile(south_edge, zoom);

var right_tile = lon2tile(east_edge, zoom);

var width = Math.abs(left_tile - right_tile) + 1;

var height = Math.abs(top_tile - bottom_tile) + 1;

// total tiles

var total_tiles = width height; // -> eg. 377*

display a given area

display a given area

Leaflet can be used to add shapes and markers to a map

I added a triangle to a Leaflet map this code:

var polygon = L.polygon([

[51.509, -0.08],

[51.503, -0.06],

[51.51, -0.047]

]).addTo(map);

adding a triangle to a Leaflet map

adding a triangle to a Leaflet map

Leaflet controls can be added to a map

A number of controls are included with Leaflet.

I used this code to add a scale control to a map:

L.control.scale().addTo(map);

scale control

scale control

Creating new controls

New Leaflet controls can be created by extending L.Control.

I created a "LocationSelect" control that allows users to "fly" between locations (based on this post)

Click here to navigate to a demo page.

travel by map

travel by map

index.html

TEXT
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7    <title>Document</title>
8</head>
9<body>
10    
11</body>
12</html>

index.html

TEXT
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5    <meta charset="UTF-8">
6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
8    <title>Document</title>
9
10    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
11        integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
12        crossorigin="" />
13    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
14        integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
15        crossorigin=""></script>
16
17</head>
18
19<body>
20    <div id="map" style="height: 100vh"></div>
21
22    <script>
23        var map = L.map('map').setView([51.505, -0.09], 13);
24
25        L.tileLayer('//services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg').addTo(map);
26    </script>
27</body>
28
29</html>

map.html

TEXT
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5    <meta charset="UTF-8">
6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
8    <title>Document</title>
9
10    <script>
11        function lon2tile(lon, zoom) { return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom))); }
12        function lat2tile(lat, zoom) { return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom))); }
13    </script>
14</head>
15
16<body>
17    <img id="tile">
18
19    <script>
20        const latlng = [51.505, -0.09]
21        const zoom = 13
22
23        const z = zoom
24        y = lat2tile(latlng[0], zoom)
25        x = lon2tile(latlng[1], zoom)
26
27        const url = `https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${z}/${y}/${x}.jpg`
28        document.getElementById("tile").src = url
29    </script>
30</body>
31
32</html>

map.html

TEXT
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5    <meta charset="UTF-8">
6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
8    <title>Document</title>
9
10    <script>
11        function lon2tile(lon, zoom) { return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom))); }
12        function lat2tile(lat, zoom) { return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom))); }
13    </script>
14</head>
15
16<body>
17    <div id="map" style="position: relative;">
18
19        <script>
20
21            // [HOW FAR NORTH, HOW FAR WEST]
22            const latlngnorthwest = [51.51, -0.08]
23            const latlngsoutheast = [51.503, -0.047]
24
25            const zoom = 13
26
27            const north_edge = latlngnorthwest[0]
28            const west_edge = latlngnorthwest[1]
29            const south_edge = latlngsoutheast[0]
30            const east_edge = latlngsoutheast[1]
31
32            const top_tile = lat2tile(north_edge, zoom);
33            const left_tile = lon2tile(west_edge, zoom);
34            const bottom_tile = lat2tile(south_edge, zoom);
35            const right_tile = lon2tile(east_edge, zoom);
36
37            console.log({ top_tile, left_tile, bottom_tile, right_tile })
38
39            const width = Math.abs(left_tile - right_tile) + 1;
40            const height = Math.abs(top_tile - bottom_tile) + 1;
41
42            const total_tiles = width * height; // -> eg. 377
43
44            console.log({ width, height, total_tiles })
45
46            const tileSize = 256
47
48            document.getElementById("map").style.width = tileSize * width + "px";
49            document.getElementById("map").style.height = tileSize * height + "px";
50
51            let innerHTML = ""
52
53            for (let top = 0; top < height; top++) {
54                for (let left = 0; left < width; left++) {
55                    toptobottom = top_tile + top
56                    lefttoright = left_tile + left
57
58                    innerHTML += `<img style="position:absolute;top:${top * tileSize}px;left:${left * tileSize}px;height:${tileSize}px;width:${tileSize}px" src=https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${zoom}/${toptobottom}/${lefttoright}.jpg>`
59                }
60            }
61            document.getElementById("map").innerHTML = innerHTML
62
63        </script>
64</body>
65
66</html>

index.html

TEXT
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5    <meta charset="UTF-8">
6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
8    <title>Document</title>
9
10    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
11        integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
12        crossorigin="" />
13    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
14        integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
15        crossorigin=""></script>
16
17</head>
18
19<body>
20    <div id="map" style="height: 100vh"></div>
21
22    <script>
23        var map = L.map('map').setView([51.505, -0.09], 13);
24
25        L.tileLayer('//services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg').addTo(map);
26
27        var polygon = L.polygon([
28            [51.509, -0.08],
29            [51.503, -0.06],
30            [51.51, -0.047]
31        ]).addTo(map);
32
33    </script>
34</body>
35
36</html>

index.html

TEXT
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5    <meta charset="UTF-8">
6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
8    <title>Document</title>
9
10    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
11        integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
12        crossorigin="" />
13    <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
14        integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
15        crossorigin=""></script>
16
17</head>
18
19<body>
20    <div id="map" style="height: 100vh"></div>
21
22    <script>
23
24        L.LocationSelect = L.Control.extend({
25            options: {
26                position: 'topright',
27                locations: []
28            },
29            onAdd: function (map) {
30                this.div = L.DomUtil.create('div', 'leaflet-siteselect-container');
31                this.select = L.DomUtil.create('select', 'leaflet-siteselect', this.div);
32                var content = '';
33
34                var locationKeys = Object.keys(this.options.locations)
35                for (let key in locationKeys) {
36                    content += '<option>' + locationKeys[key] + '</option>';
37                }
38
39                this.select.innerHTML = content;
40                this.select.onmousedown = L.DomEvent.stopPropagation;
41                return this.div;
42            },
43
44            on: function (type, handler) {
45                if (type == 'change') {
46                    this.onChange = handler;
47                    L.DomEvent.addListener(this.select, 'change', this._onChange, this);
48                } else if (type == 'click') { //don't need this here probably, but for convenience?
49                    this.onClick = handler;
50                    L.DomEvent.addListener(this.select, 'click', this.onClick, this);
51                } else {
52                    console.log('LocationSelect - cannot handle ' + type + ' events.')
53                }
54            },
55
56            _onChange: function (e) {
57                var selectedLocation = this.select.options[this.select.selectedIndex].value;
58                e.details = this.options.locations[selectedLocation];
59                this.onChange(e);
60            }
61        })
62
63        L.locationSelect = function (id, options) {
64            return new L.LocationSelect(id, options);
65        }
66
67    </script>
68
69    <script>
70
71        const locations = {
72            "London": { latlng: [51.5283063, -0.3824737], zoom: 9 },
73            "Auckland": { latlng: [-36.8594804, 174.4252343], zoom: 9 },
74            "Sydney": { latlng: [-33.8473567, 150.6517868], zoom: 9 },
75            "New York": { latlng: [40.6976701, -74.2598693], zoom: 9 }
76        }
77
78        const startcity = "London"
79        var map = L.map('map').setView(locations[startcity].latlng, locations[startcity].zoom);
80
81        L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
82
83        var select = L.locationSelect({ position: 'topright', locations }).addTo(map);
84
85        select.on('change', function (e) {
86            if (e.details === undefined) {
87                return;
88            }
89            this._map.flyTo(e.details.latlng, e.details.zoom)
90        });
91
92    </script>
93</body>
94
95</html>

References