Country border highlighting with Leaflet.js

Not too long ago I finished a project on Big Data for the University. One part of this huge project was the implementation of a dynamics map that would be responsive to mouse hover and mouse click. When the mouse would hover over a country, the whole country borders would be highlighted in the map, and if the user clicked inside those borders, we would display tons of interesting data using D3.js.

For map displaying and operations on Google Style maps, look no further than Leaflet.js.

All examples I found on the Internet would either not provide proper callbacks, or would not provide these features for the entire world map. Providing callbacks for a world map is extremely difficult in terms of performance optimisation, for reasons that will be explained below. For now, let’s briefly explain how it works.

Mapping Systems

Mapping representation systems in general are based on tiling. In a server somewhere in the Universe there is an enormous number of square tiles that represent different parts of the globe in different zoom levels. These tiles have been created by companies that are dedicated into topography and map creation, usually with tools such as the ones provided by ArgGIS. A library like Leaflet (or Google Maps, or Apple) works by performing requests to this server, with X, Y, Z (zoom level) as arguments. The server responds with square images representing parts of the map requested, and it is up to the framework to place these tiles in place in a viewport. There are many servers which provide tiles for these purposes, called “Tile Map Services“.

Most map javascript web applications are tied into a specific service (like Google’s) but the advantage of Leaflet.js is that the tile url is provided as an argument upon creating a layer, so it can get tiles from many services.

Creating a map with Leaflet

Leaflet works with layers. Inside a viewport, leaflet applies one layer on top of each other. A layer for leaflet can be anything, from a map to an SVG element, or a pin image. Usually, the first layer that is created is the layer that holds the map tiles. All other layers will come on top of it.

 

The mapDivName is the name of the HTML element that we are going to create our map in. We are creating a map with the coordinates “20,0” (latitude, longitude) as the centre, with the specified zoom level. Optionally, we can set the map bounds to something that we prefer, to avoid infinitely scrolling left and right and seeing many versions of the same map (Leaflet has this little “problem”, although many would consider it a “feature”).

The first layer that we create is a tile layer, with tiles taken from opencycle map. The {z},{x},{y} attributes are used to get tiles at specific coordinates and zoom level. Try copying the url, pasting it into your browser’s address field and giving different numbers as arguments to those fields and see the results.

Up until now, you should be able to see a map, that works a bit like Google Maps.

Adding the country highlighting layer

We want to add now a country highlighter. When the mouse moves over a country, it should be able to highlight the borders of the country (and the interior). The image below shows France highlighted.

map_france_highlight

For this we must add a new type of layer on top of the tile layer, a GeoJSON layer, that will provide Leaflet with bounding polygons, so that we can attach our callbacks to them.

For attaching such a layer, we should find a GeoJSOn file containing country borders. In many cases, such a GeoJSON file is not available. Fortunately, there a way to produce a GeoJSON file by taking a shape file produced by an ESRI-format-compatible tool. Personally, I found a shape file which contains all country borders, here:

http://thematicmapping.org/downloads/world_borders.php

The next step is to convert its contents to GeoJSON format. The shape files are usually accompanied by other files which the same prefix but different suffix. We need those files, as they are descriptors about the polygons contained in a shape file.To convert all of these files into a single GeoJSON file, you can either use ogr2ogr in your local machine, or an OGR2OGR web service such as this one. ogr2ogr is part of the enormous GDAL package containing utilities for working with shape polygons, and maps.

You can choose whatever you want, however, be aware: The resulting GeoJSON file should be small enough to cope with the limited performance capabilities of Leaflet, Javascript and Firefox. If you use the same file I linked in this post, the resulting file will cripple the performance of leaflet in Firefox, as well as any browser on any mobile platform. For this reason, I personally prefer to use my local ogr2ogr utility, and produce a lower-resolution file, that better suits my needs. After we have installed GDAL (whose installation is out of the scope of this article), we can open a terminal, and execute the following command:

The COORDINATE_PRECISION argument is used to cut the precision of each polygon vertex in each country resulting in a smaller JSON file, and a much more improved performance for browsers with limited capabilities.

Now we are ready to load the GeoJSON file as a layer. Personally, I used D3 to load the JSON asynchronously.

Note that the “C” prefix is a custom prefix defined by me. I keep the C.geojson layer created, because later I will need to access it from different parts of the code.

We create the GeoJSON layer, and we pass as arguments a function to call when certain events happen in each polygon that is contained in the GeoJSON. We also pass as an argument the default style adopted by each polygon in the GeoJSON. This will apply this style to all polygons that are displayed on top of the map.

In the “onEachFeature” class, we define the attributes and the events supported by each of the polygons in this layer. I personally passed as arguments 3 functions, whose definitions are given below:

As you can see, we set a style and unset it depending on wether the mouse hovers over a country polygon. The code is fairly simple, and in case you need to provide additional callbacks, you can add them to the bottom of these three functions.

The first thing I should explain is the properties “iso_a2” and “name” of the properties. Those properties come from the GeoJSON file itself. I am attaching one country’s description from the file in JSON format:

The “geometry” object is taken from the original shape file itself. The other properties, however, are taken by combining the original .shp file with the accompanying .dbf file. And that’s the reason why you should not get rid of these files yet. The names of the attributes can be acquired by examining these files with GDAL, or by looking at the resulting GeoJSON file.

That’s it!

I’m not good at writing tutorials, but I really wanted to share this with others. It’s not that the code itself is something strange or difficult, but other sources I have found on the internet either followed processes that apply only to a specific area on a map (thus not needing shape file normalisation), or did not work well with firefox (Firefox is really slow with SVG layers, at least until today’s version).

Mapping in the digital world is a huge and difficult concept and libraries such as Leaflet.js simplify the process of using maps and some concepts surrounding them. I hope I helped some newbies avoid the holes that I fell into when I started to use Leaflet.

Comments are welcome.

 

  • Adam Bramley

    Hey great tutorial, one thing I’ve been trying to get to work is similar to the hover highlighting but to highlight when clicked and reset to default style when clicked off. The difficult part comes when you start handling the case where a user clicks another region when one is already selected, and when they click the same region twice. Would love to see a solution to this!

    • csotiriou

      Hello, Adam.

      For this, you will probably need to use ‘function onCountryClick(e)’ as indicated above (although not used). I think you will have to hold a reference always to the currently selected country polygon, so when a click happens, you reset to the default style the old one, set the new style to the new polygon, and then set the reference to the new polygon. That will work (although not tested) in both of the test cases you mentioned.

  • xyzzy-plugh

    Thanks for this. Do you have a link to the live version or a demo?

    • csotiriou

      I have to ask my professors for that, as some data we used are not meant to be public.

      • xyzzy-plugh

        I am getting an error: Globals is not defined. Where do you define it?

        • csotiriou

          Globals is a global variable, defined in another file. I opted to not include it, since it’s irrelevant with the implementation, it’s just some convenience functions written by me.

  • Great! Thanks a lot for filling in the blanks left in the Leaflet docs on http://leafletjs.com/examples/choropleth.html and http://leafletjs.com/examples/geojson.html. Vice versa, those docs come in handy as a demo.

    I found http://www.naturalearthdata.com/downloads/110m-cultural-vectors/110m-admin-0-countries to offer more accurate country border data.

    There’s a similar .shp in the download they offer; your workflow worked just as well when I used their data.

    To set up ogr2ogr on Windows, I followed the instructions on http://cartometric.com/blog/2011/10/17/install-gdal-on-windows.

    Keep up the good work.

    • csotiriou

      My only problem with this dataset was Singapore, which was too small or missing.

      • Ah, yeah, you’re right!

        Then again, at least The Netherlands don’t just look like a jumble of lines.

        Pros and cons…

        • csotiriou

          Yeah, I could live with very small areas not being visible, if Singapore wasn’t actually where my professor was coming from… and this project was part of a bigger one for his course 🙂

          • Yeah, I guess it’ll be always like that – picking the one that suits you/the project best 🙂