Leaflet

Neil HaddleyFebruary 21, 2022

An open-source JavaScript libraryfor mobile-friendly interactive maps

Mapsgis

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.

I used emmet

I used emmet

I created a template HTML page

I created a 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 replaced the div element with id 'map' with the Leaflet map:

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

Here I added the arcgisonline.com tiles:

JAVASCRIPT
1L.tileLayer('//services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg').addTo(map);
I viewed index.html from localhost

I viewed index.html from localhost

I deployed index.html to Azure Storage

I deployed index.html to Azure Storage

I created a new Storage Account

I created a new Storage Account

I provided a unique name

I provided a unique name

The Storage account was created

The Storage account was created

I clicked the Browse to website button

I clicked the Browse to website button

I viewed index.html from Azure on my laptop

I viewed index.html from Azure on my laptop

I viewed index.html from Azure on iPhone (Safari)

I viewed index.html from Azure on 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).

I reviewed network requests

I reviewed network requests

I reviewed a single tile from arcgisonline.com

I reviewed 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:

I displayed a given location

I displayed 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:

JAVASCRIPT
1var zoom        = 9;
2var top_tile    = lat2tile(north_edge, zoom); // eg.lat2tile(34.422, 9);
3var left_tile   = lon2tile(west_edge, zoom);
4var bottom_tile = lat2tile(south_edge, zoom);
5var right_tile  = lon2tile(east_edge, zoom);
6var width       = Math.abs(left_tile - right_tile) + 1;
7var height      = Math.abs(top_tile - bottom_tile) + 1;
8
9// total tiles
10var total_tiles = width * height; // -> eg. 377
I displayed a given area

I displayed a given area

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

I added a triangle to a Leaflet map using this code:

JAVASCRIPT
1var polygon = L.polygon([
2            [51.509, -0.08],
3            [51.503, -0.06],
4            [51.51, -0.047]
5        ]).addTo(map);
I added a triangle to a Leaflet map

I added 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:

JAVASCRIPT
1L.control.scale().addTo(map);
I added a scale control

I added a 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.

I created a travel by map control

I created a travel by map control

index.html

HTML
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

HTML
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

HTML
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

HTML
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

HTML
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

HTML
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