Creating a Leaflet.js mapping app from the ground up

One of the easiest ways to make a JavaScript-based online map is using the Leaflet.js library. The library was designed by Vladimir Agafonkin with, in his words, “simplicity, performance and usability in mind”. There are a number of Leaflet code examples but we thought our readers might benefit from an example where we build a map from the ground up including key bits of code to help you filter, read data from an external file, add a hover and change icons. The map we’re making looks like this:

Let’s get started.

1. Build your boilerplate Leaflet map and create your point data file

Leaflet allows you to use a variety of base maps. We tend to like MapBox tiles and will add these tiles using the Leaflet function tileLayer. Note that the examples.map-i87786ca indicates that we’re using example tiles from MapBox but you can replace this with the ID of your own map tiles. For now we will manually set the center and the zoom level.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
    <script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
    <script src='https://api.tiles.mapbox.com/mapbox.js/v1.6.4/mapbox.js'></script>
    <style type="text/css">
    #map {
        width: 600px;
        height: 400px;
    }
    </style>
</head>
<body>
    <div id="map"></div>

    <script type="text/javascript">
    var mapboxTiles = L.tileLayer('https://{s}.tiles.mapbox.com/v3/spatial.b625e395/{z}/{x}/{y}.png', {
        attribution: '<a href="http://www.mapbox.com/about/maps/" target="_blank">Terms &amp; Feedback</a>'
    });

    var map = L.map('map')
        .addLayer(mapboxTiles)
        .setView([40.72332345541449, -73.99], 10);
    </script>
</body>
</html>

In a later step we will add a few markers to the map so let’s create a geoJSON file with the marker details. We will manually create the geoJSON file but this helpful utility can make it easier. We are saving this code as a separate file (not inline) to show how to read external data. The file will be called businesses.json. Here is what our file looks like:

{
    "type": "FeatureCollection",
    "features": [

        {
            "type": "Feature",
            "properties": {
                "Name": "Gimme! Coffee",
                "BusType": "Cafe",
                "Description": "Gimme espresso bars are found in New York City and Upstate New York."

            },
            "geometry": {
                "type": "Point",
                "coordinates": [-76.499491, 42.444508]
            }
        }, {
            "type": "Feature",
            "properties": {
                "Name": "ZevRoss Spatial Analysis",
                "BusType": "Data analysis and mapping",
                "Description": "Specializes in spatial and environmental data analysis and statistics, geographic information systems and custom data design."
            },
            "geometry": {
                "type": "Point",
                "coordinates": [-76.4958259, 42.440258]
            }
        }, {
            "type": "Feature",
            "properties": {
                "Name": "Argos Inn",
                "BusType": "Inn and Bar",
                "Description": "Inn and cocktail bar in a beautifully restored historic mansion."

            },
            "geometry": {
                "type": "Point",
                "coordinates": [-76.492543, 42.439401]
            }
        }


    ]

}

This is a good start, here is what my map looks like so far (without the markers yet):

The full code for the boiler plate (without the geoJSON) is here.

2. Read data from the file you just created

Most of the Leaflet examples available on the web demonstrate how easy it is to add a marker to the map. For example, with this super simple code you can add a marker to your map:

var marker = L.marker([42.444508,-76.499491]).addTo(map);

But more often than not you will want to read the marker details from an external file. To do this we will use jQuery’s function getJSON(). After adding a reference to jQuery in the header (see the full code for this step if this is unfamiliar to you) we set up the code to read our data. Here we’re using a promise instead of a callback. You can read more about why we’re using a promise from this blog post by David Walsh. Note that this code reads the data but does nothing with it (yet!). Here is the data-reading snippet:

var promise = $.getJSON("businesses.json");
promise.then(function(data) {
    //do a bunch of stuff here
    console.log(data) // take a look at the data in the console
});

The full code for this step is here.

3. Add markers and zoom

Above we manually set the zoom level and the view center but we would rather do this programatically. We would also like to add our markers to the map. We will create a geoJSON layer from our data using the geoJson method. And then we will use fitBounds to fit the extent to our markers. Finally, we will add some padding to ensure that our markers (which will extend beyond the extent of our points) don’t get cut off. Keep in mind that all of this can only happen after we have loaded our data so the code must be included within our promise function. The following code (added within the promise/getJSON function) fulfills our goals:

var allbusinesses = L.geoJson(data);
allbusinesses.addTo(map);

map.fitBounds(allbusinesses.getBounds(), {
    padding: [50, 50]
});

The full code for this step is here.

4. Filter your data

We will want to have subsets of markers that we can allow the user to turn on and off. For this example let’s use cafes vs non-cafes. The geoJson method in Leaflet has a filter option that we can use to break the data into cafe’s and non-cafes. We can then add each of these pieces to the map. We will distinguish the icons in a later step. Here is an example of using the filter:

var cafes = L.geoJson(data, {
    filter: function(feature, layer) {
        return feature.properties.BusType == "Cafe";
    }
});

And then you can add your subset of points using addTo:

cafes.addTo(map)

Our map has not changed yet so we will not show the map again here. The full code for this step is here.

5. Different icons for different types of points

I’ll admit that I find changing the icons in Leaflet to be a little cumbersome, but there is a nice plugin by Lennard Voogdt to help us. The Leaflet.awesome-markers plugin will allow us to color-code our markers and, more importantly, to use glyphs from either Bootstrap or, in our case, Font Awesome.

In this example, we want to have different icons for different business types (cafes vs non-cafes). There are two parts to doing this: (A) you need to create the icon itself and (B) you need to apply it to your geoJSON layer. To get up and running with the plugin, I suggest reading the steps on the plugin GitHub page but here is our summary:

Add a reference to Font Awesome (or Bootstrap if you’re using Bootstrap glyphs):

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">

Then you need to download and reference the plugin CSS and JavaScript (there is no CDN). And you need to make sure to add these after Leaflet and Font Awesome. You will also need to download the images and be sure to change the urls in the plugin CSS. My header links now look like:

<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet"><!--this is new-->
<link rel="stylesheet" href="leaflet_awesome_markers.css"><!--this is new-->

<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src='https://api.tiles.mapbox.com/mapbox.js/v1.6.4/mapbox.js'></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="leaflet_awesome_markers.js"></script><!--this is new-->

With the references in place you can now create the icon following the instructions on the GitHub page. You choose whether you want a Font Awesome or Bootstrap glyph, the color (you are limited to red, darkred, orange, green, darkgreen, blue, purple, darkpuple, cadetblue) and then you identify the glyph by name. As in:

var cafeIcon = L.AwesomeMarkers.icon({
    prefix: 'fa', //font awesome rather than bootstrap
    markerColor: 'red', // see colors above
    icon: 'coffee' //http://fortawesome.github.io/Font-Awesome/icons/
});

Once you have your icon defined you use it with the Leaflet pointToLayer option:

var cafes = L.geoJson(data, {
    filter: function(feature, layer) {
        return feature.properties.BusType == "Cafe";
    },
    pointToLayer: function(feature, latlng) {
        return L.marker(latlng, {
            icon: cafeIcon
        })
    }
});

Let’s take a look. Not bad!

The full code for this step is here.

6. Add a hover event

We would like the business names to be displayed when my users hover over the markers. We’ll add a mouseover event after creating the geoJson layer (see below where we have .on('mouseover' function(){})).

var cafes = L.geoJson(data, {
    filter: function(feature, layer) {
        return feature.properties.BusType == "Cafe";
    },
    pointToLayer: function(feature, latlng) {
        return L.marker(latlng, {
            icon: cafeIcon
        //this part is new
        }).on('mouseover', function() {
            this.bindPopup(feature.properties.Name).openPopup();
        });
    }
});

Try mousing over our icons…

The full code for this step is here.

7. Final step: Add buttons to filter points

As a final step, let’s add buttons to let our users filter the markers. For a more (much more) advanced version of filtering you can see our post on using Angular’s two-way data binding. Here we will keep it simple. We’re going to use Bootstrap buttons so the first step is adding a reference to Bootstrap:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">

Now we can add the buttons themselves using HTML. I’m adding three buttons, one for cafes, one for ‘others’ and one for all:

<div class="btn-group">
    <button type="button" id="allbus" class="btn btn-success">All</button>
    <button type="button" id="others" class="btn btn-primary">Others</button>
    <button type="button" id="cafes" class="btn btn-danger">Cafes</button>
</div>

The final step is to hook up the buttons using jQuery. You’ll note that we’re using the button’s click method and once the user clicks we add the clicked layers and remove the others. Here is the cafe button example:

$("#cafes").click(function() {
    map.addLayer(cafes)
    map.removeLayer(others)

});

And that’s it. We now have a fully functioning app using Leaflet.js.

The full code below (click the GitHub link at the bottom of the code if you want all the code and data, including references).

9 responses

  1. very nice and straightforward. but how would i do something like that using an image instead of a map? and maybe a couple of images layered. for example, image layers to have a before and after effect?

    • I really appreciate you letting me know about this issue! This was due to changes with MapBox tiles we were using. Now we point to CartoDB tiles and you should see the maps.

Leave a Reply

Your email address will not be published. Required fields are marked *