Creating An Interactive Map With Leaflet and OpenStreetMap

posted 23 Jan 2014 by Andy Maloney

I’ve known for a while that the interactive map of the world showing some of my bloodstain pattern analysis software customers was kind of slow. I also knew that I was using a very outdated version of Google’s API for displaying the data. Yesterday I decided to take a look at it to see what I needed to do to fix it up.

Old Google Map

My first stop was the Google page about moving to v3 of the API. Apparently I needed to get a new API key by signing up for something with my Google Account. Google has added usage limits and now tracks absolutely everything (yes, they probably did before, but now it’s more explicit). I also needed to consider whether or not I can use the maps on a commercial site.

I don’t like hurdles, and I like simplicity, so I decided to look around for alternatives. I remembered looking at OpenStreetMap (OSM) several years ago when I originally built my map and deciding it wasn’t quite good enough. I decided to check it out again to see how they were doing. My how things change!

OpenStreetMap

Because I’m a good lazy developer (by that I mean I avoid writing things when they already exist, are well-coded, and maintainable), my first thought was to grab a WordPress plugin and to use it for my map. I tried out the OSM – OpenStreetMap plugin. While it sort-of worked for what I was trying to do, it was not as customizable as I’d like, it seemed too heavy for one map on one site, and it tied me to WordPress (which my other site hasn’t moved to yet).

My next thought was to look for a JavaScript library with a nice API to integrate and handle most of the heavy lifting. Poking around the OSM site, I ran across OpenLayers. It looked pretty powerful, and has a ton of stuff in its API. I downloaded it (10M?) and took a look but I really just wanted something simpler. Then I found Leaflet. Exactly what I needed. Small, simple, great documentation, and some really straightforward examples.

Leaflet

Even though I had to brush up on some JavaScript, jQuery, and JSON concepts—things I rarely use— I ended up researching possibilities and replacing my Google Map with an OpenStreetMap/Leaflet version in just a couple of hours. It’s now much faster and simpler to customize.

These lines pull in the CSS and JavaScript libraries we need to display and work with the maps. In this example, we are linking to these files stored on a remote server using a CDN. Another option would be to host these files ourselves, but the advantages of using these CDNs are:

lightens the load on your server

CDNs distribute the content so it’s closer to the user, resulting in faster downloads

browsers limit the number of HTTP requests to each host, so accessing multiple, different hosts allows fetching data in parallel, resulting in faster display of the page (we’ll see this idea again with the tiles which make up the map)

if a user has visited another site using the same script, it may already be in the user’s cache resulting in no download at all and faster display of the page

This example uses cdn.leafletjs.com to get the Leaflet CSS and library and ajax.googleapis.com to get the jQuery library. Note that leaving the protocol off the Google URL is not an error – this is a protocol relative URL (a.k.a. scheme-less URI).

(Update: I was using MAMP to write and test this. It’s the web server which adds the correct protocol to scheme-less URIs, so if you just open the file on its own, browsers will not add it properly. You will need to add http: to the ajax.googleapis.com URI for this to work. I have updated the download to fix this.)

The next interesting part of this HTML file is this div:

XHTML

16

<div id="map"style="height: 440px; border: 1px solid #AAA;"></div>

We’re creating a div called “map” with a height and a border. This is the div that is going to be filled in with our map when we create it in our JavaScript. If we do not specify a width, it will adjust the width of the map to the page dynamically.

The last part of this file is the key. This is where we load the JavaScript that does the work of setting up and displaying our map.

XHTML

18

19

<script type='text/javascript'src='maps/markers.json'></script>

<script type='text/javascript'src='maps/leaf-demo.js'></script>

The first script is a JSON file with our marker information and the second is the code to setup and display our map. Note that the second script has to come after the map div is created so the JavaScript has something to target. In this case, we’re loading a local file, so let’s take a look at that next.

The Leaflet JavaScript

Our initial JavaScript is pretty straightforward. All we are doing here is creating the map and adding a tile layer. For the purpose of my map, I liked the tiles from MapQuest and they provide a nice interface to allow OSM users to display them.

JavaScript

1

2

3

4

5

varmap=L.map('map',{

center:[20.0,5.0],

minZoom:2,

zoom:2

});

This creates a new map, assigns it to the ‘map’ div and sets some options. In this case we are centering the initial view at 20° latitude and 5° longitude, setting the minimum zoom level to 2 and the default zoom level to 2. There are many other options you can set here to tweak the way you want your map displayed.

This code adds a layer to the map telling it what set of tiles to display and where to get them. There are several different servers you can use – or you can host your own.

Again, Leaflet has many options to use when creating a tileLayer. In our example, the first argument is the URL template so Leaflet knows how to fetch the tiles from the servers properly. Next is the attribution – this is what shows up in the bottom-right corner of the map. It is important that you add the right info here for proper attribution of the tile set.

Finally we have a list of subdomains. This array of strings is substituted into the {s} portion of the URL template. Note that this list depends on the tile servers you are using – if you use the OSM tiles at openstreetmap.org, this will be a different list. This option allows a browser to download from several servers at the same time (remember I mentioned this when explaining CDNs above?) which results in faster loading of the page and faster updates as the user drags around the map. One of the problems I had with the old Google solution is that it only used one server, so it was slow. This capability makes it much faster.

OK, so now we have an interactive map we can drag around and zoom in and out. Let’s add some markers to it.

The JSON Data

Although it is possible to put the marker data right in with our JavaScript file, I like keeping my data in a separate file so it’s easy to swap out or update without modifying the main file.

For this example, I’m using JSON as a little database to store the country name, Wikipedia url, and longitude and latitude of each of the countries I’ve either lived in or travelled through for a month or more.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

markers=[

{

"name":"Canada",

"url":"https://en.wikipedia.org/wiki/Canada",

"lat":56.130366,

"lng":-106.346771

},

{

"name":"Anguilla",

"url":"https://en.wikipedia.org/wiki/Anguilla",

"lat":18.220554,

"lng":-63.068615

},

...

{

"name":"Japan",

"url":"https://en.wikipedia.org/wiki/Japan",

"lat":36.204824,

"lng":138.252924

}

];

To read this data and add the markers to our map, I just loop over the array and call the Leaflet function to create markers:

Leaflet’s markers offer many options for customization. In our example, we are creating a marker at a specific location, binding a simple popup to it, and adding it to our map. The latitude and longitude and the data to display in the popup (the country name and link to Wikipedia) are read from our JSON file.

So far, so good, but those markers are kind of big, so let’s swap them out for our own icons.

Changing The Icon

I found a free pin icon over at clker.com that I thought would look good on this map. I played around a bit with different sizes and settled on a height of 24 pixels. I created two versions: the regular version with a height of 24 pixels and a double-sized version for retina displays with a height of 48 pixels. And of course I ran these through ImageOptim to compress them as much as possible.

Since we are going to add our own icon, the code we did in the last section needs to be changed a bit. Here’s what we have now:

In this code, the first thing I’m doing is grabbing our URL and removing the file name so I can create a URL for the icons. Not being a JavaScript guru, I’m not 100% sure this is the best way to do this, and it feels hacky, but it does work. (If you know of a better way, please contact me!)

The next line declares a var myIcon to hold our new icon. Leaflet’s icon allows you to specify all sorts of things, but the key ones are the ones we use here. The iconUrl, iconRetinaUrl, and iconSize options are self-explanatory. The iconAnchor is the pixel location in the icon file that corresponds to the specific latitude and longitude. The popupAnchor specifies where the popup should be located relative to the iconAnchor.

One last thing to do with our new icon is actually apply it. To do that, we simply add it as an option when creating our markers {icon: myIcon} (line 24).

Voilà!

Final Result

Here’s our final result – an interactive map indicating the countries I’ve either lived in or travelled through for a month or more.

I hope this is useful to someone out there in internet-land!

As always, if you have suggestions on how to improve my code or make things easier, please comment below or feel free to contact me.

57 Comments

Hi Andy,
thanks for the tutorial – just a short remark: you might be interested in also trying another WordPress OSM plugin: search for “Leaflet Maps Marker” in the WordPress plugin repository (free version) or download the pro version from http://www.mapsmarker.com – my plugin allows users to pin, organize & show your favorite places & tracks through OpenStreetMap, Google Maps, KML, Bing Maps, APIs or Augmented-Reality browsers
best regards,
Robert

If you mean using a different, smaller set of tiles for a region, I think you could just substitute them in when creating the tile layer (see the Javascript code calling L.tileLayer()).

If you mean using one of the provided tile sets and restricting the view to a specific region, then in the Javascript code where you create the map (just under the The Leaflet JavaScript heading) you can set the location to the region you want and play with the minZoom and maxZoom options to restrict it.

I’m afraid I can’t tell what’s wrong with your code. I would suggest starting with the example, replacing the JSON with yours, making sure that works for you, and then adding in the new code bit-by-bit to figure out what’s not working for you.

Thank you Andy. I’ve decided that presenting a rectangle control for letting the user select a bounding box using Leaflet or OpenLayers is nice, but doesn’t worth the effort (I’ve found how to do it).Instead, I’m telling the user that holding down the shift key while dragging the mouse can be used for selecting a bbox. I’ve added a popup which tells the user what are the current LatLon values, while the shift key is pressed.
Upon zoomend: The selected area occupies all the redrawn map, and I have the coordinates using map.getBounds().getNorth(), etc.
Upon mouseup: map.closePopup();

Hi, Andy! Thanks for the tutorial, it’s helped a lot. This is my first experience using JSON at all, so I was wondering if you could help me out with the pop-ups my markers.json file outputs.

Currently I have Name | URL | Lat | Long for each marker, exactly like in your tutorial. But if I’d like any other text to my pop-up, it seems like I need to add into the “name”, perhaps with a little html syntax. But it’s still linked the marker’s URL. Any suggestions? Thanks again.

I want to attempt something very similar to this, but with the added ability to let any user add their own pins for new projects (the site will on a LAN, not the internet). What would be the easiest way to add this functionality?

Doing this requires some form of persistent storage for the data. This could be reading & writing the JSON file itself or storing it in a database.

How you approach it would depend on what languages and tools you have available, how robust you want your solution to be, the scope of the project (how many locations? how many people accessing it?, etc.), and how much time & effort you want to put into it.

Using a database—if you have one available—would probably be the safest and easiest solution. Then you don’t have to worry about multiple people trying to update the same file on the filesystem at the same time…

OK, and if I wanted the .json file/markers to point to a folder on a server within our domain instead of a URL like your example, how do I make this change in the .json file? BTW great tutorial! I am in way over my head but you made a non-coder feel really smart

As I mention in a comment above, doing this requires some form of persistent storage for the data. This could be reading & writing the JSON file itself or storing it in a database.

I assume you want this available to the internet, not just an intranet (as in the comment above). I think if I were approaching this problem, I would create a little ReSTAPI and use it from javascript to read & update markers.

My code doesn’t have a license – you can do whatever you want with it.

Leaflet and OpenStreetMap have their own license as do any tile sets you use, so you will have to look into that. My understanding is that they just require a note with links on the map, but I am not a lawyer.

thank you, this is what i need for a kickstart. this simple tutorial is what I was looking for.
I had to make a small change in the iconsURLs, had to make it relative to the index: “maps/images/pin24.png”. since i’m not using mump, the jquery wasn’t working.

thanks for sharing your brilliant example with us.
i have understood how to put markers on desired locations usina a JSON file.

but my query is.
if i am searching for places from for ex. -google maps. we get markers for those locations. this same thing i want to implement in here, such that i will get data in lat and long for places, how to add markers for those places ?

Hi Andy, thanks for a brilliantly explained tutorial.
I’m wondering : How can one go about converting a simple excel csv /tsv having the same kind of data : place name, a link, longitude and latitude; and importing that here? Is there a way to convert spreadsheet data into the JSON format you used? Because it’s much simpler for humans to make the table than a JSON.

You can either convert it to JSON “offline” then just use the JSON, or you might consider modifying the code to just read CSV files and convert to JSON using Javascript. It depends on if you need to do it once or multiple times.