How good is: Leaflet.js
Tl;dr
- You can easily get a map based application running with few dependencies.
- Much more permissive & customizable than corporate mapping libraries.
- Large community with many plugins.
About Leaflet
Leaflet.js is a library for adding interactivity to maps. They have a ton of features and plugins to support doing pretty much anything with a map that you can think of.
The Application
Note: I’ll be using ES6 syntax in this post (arrow functions, template strings and a couple other small things). If you aren’t familiar, check out this resource.
Using data from: bikesharetoronto.com, we’ll build a realtime bike station visualizer. The data which I am referencing is: feeds.bikesharetoronto.com/stations/stations.xml
That xml url is updated every time somebody takes out a bike, it also provides handy latitude and longitude coordinates for each of the bikes stations.
This is what the completed project looks like:
The more green a marker corresponds to how full a station is (how few empty slots there are). The entire application is updated in real time via polling the xml.
The application is available to see at:
https://bikemap-43dbc.firebaseapp.com/
and on my github: https://github.com/emars/bikemap.
Side Note
What is a bike station?
Getting Started
Create a new index.html file that we will be using for the base of the project:
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.1/dist/leaflet.css" />
<link rel="stylesheet" href="bikemap.css" />
</head>
<body>
<div id="map"></div>
<script src="https://unpkg.com/leaflet@1.0.1/dist/leaflet.js"></script>
<script src="bikemap.js"></script>
</body>
</html>
Now the styles in bikemap.css which make the map fit any browser window:
html, body, #map {
height: 100%;
width: 100%;
}
Setting Up The Map
Now to set up the map we’ll need access to a tile map, this is the actual map layer (how it looks). There are a number of tile map providers depending on what you’re looking for. I’ll be using Mapbox because it was fast to get up and running and I liked to look of their dark tile map.
All of this code is located in bikemap.js:
var TORONTO_COORDS = [43.653, -79.383];
var ZOOM_LEVEL = 13;
var MAP_ID = 'map';
var MAPBOX_ACCESS_TOKEN = 'your.mapbox.access.token';
var MAPBOX_PROJECT_ID = 'your.mapbox.project.id';
var MAPBOX_DARK_TILEMAP = 'https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/256/{z}/{x}/{y}@2x?access_token={accessToken}';var map = L.map(MAP_ID).setView(TORONTO_COORDS, ZOOM_LEVEL);L.tileLayer(MAPBOX_DARK_TILEMAP, {
maxZoom: 18,
id: MAPBOX_PROJECT_ID,
accessToken: MAPBOX_ACCESS_TOKEN
}).addTo(map);
Pretty sweet looking setup so far with very little work.
Fetching and Parsing Data
Now we need to actually pull the data from the feed, I set up a small proxy resource on heroku called: Resourcely (super creative). This simply converts the xml to JSON and sets the appropriate CORS headers so that we can request it from the client. Feel free use Resoucely or to set up your own service to handle that too.
I also set up a helper function parseStation() to get all the required information from each station into the proper format.
const BIKE_DATA_URL = 'http://feeds.bikesharetoronto.com/stations/stations.xml';const requestURL = 'https://resourcely.herokuapp.com/r/' + encodeURIComponent(BIKE_DATA_URL);function fetchStations(){
return fetch(requestURL)
.then(res => res.json())
}function populateMap(){
fetchStations()
.then(data => {
// parse the data
const stations = data.stations.station.map(parseStation);
console.log(stations); });
}function parseStation(station){
var numBikes = parseInt(station.nbBikes[0]) return {
numBikes, id: station.id[0],
name: station.name[0],
latlng: [station.lat[0], station.long[0]],
totalDocks: numBikes + parseInt(station.nbEmptyDocks[0])
}
}populateMap();
Creating Markers
Now we can place markers onto the map which correspond to each bike station location, we also easily add a popup so that a user can click on a station to get details about it. This is fairly trivial using Leaflet:
function populateMap(){
fetchStations()
.then(data => {
const stations = data.stations.station.map(parseStation);
// Add a line here:
stations.forEach(addMarker);
});
}function addMarker(station){
var stationMarkerOptions = {
radius: 50
}; var popupContent = '<h5>' + station.name + '</h5>'
+ '<p>' + station.numBikes + '/' + station.totalDocks
+ 'bikes</p>'; L.circle(station.latlng, stationMarkerOptions)
.bindPopup(popupContent)
.addTo(map);
}
Now the application should have a bunch of neat markers that are populated that corresponds to the location as well as has a popup for each marker, pretty powerful for ~50 lines of code.
Color Coding
It’d be great if the marker colors could represent how many bikes are at a station so we’ll do that next. First we write a simple helper to output an rgb string based on how full a station is:
function mapColor(ratio){
const amount = Math.floor(255.0 * ratio);
return `rgb(${255 - amount}, ${amount}, 0)`;
}
Then we add a couple lines to addMarker():
function addMarker(station){
// ADD THIS
var stationColor = mapColor(station.numBikes / station.totalDocks); var stationMarkerOptions = {
radius: 50,
color: stationColor // ADD THIS
}; var popupContent = '<h5>' + station.name[0] + '</h5>'
+ '<p>' + station.numBikes + '/' + station.totalDocks
+ 'bikes</p>'; L.circle(station.latlng, stationMarkerOptions)
.bindPopup(popupContent)
.addTo(map);
}
Make it Real Time
Now to changes show up while the user is on the page, just simply start polling:
// Polling the data
populateMap();
setInterval(populateMap, 5000);
Summary
This tutorial took you through building a very simple application with Leaflet. However, there is much more you can do with the framework.
Here are a few ideas that I’ll be working on in the future to improve this application:
- Add a real time log that shows which stations are receiving / losing bicycles.
- Add snapshots of the map so people can compare different times of day.
- Add animations to the map markers that pulse.
Going Further
This post just outlined what Leaflet is capable of, here are some more resources on the library: