Tech Blog 7: Plot a map with Eurostat NUTS2 regions in 7 easy steps

During our continued work on the C3S Energy project (a component of Copernicus Climate Change), we’ve been using Eurostat NUTS areas (nomenclature of territorial units for statistics) to define our European regions, both at country (NUTS0) and regional (NUTS2) levels.

Whilst there are plenty of resources available online for identifying the NUTS0 areas, there isn’t much out there detailing the NUTS2 regions and where they appear on a map.

After some research and experimentation at the behest of one of our colleagues, we were able to generate the resource here as a high resolution png file. Read on to find out how we did it.

How to show NUTS2 regions on a map

In this tutorial we will go though a workflow to produce a map with labelled regions. This should work for any geographic shapefiles.

Step one

Using Jupyter Notebooks, import the following packages:

[pastacode lang=”python” manual=”%23%20import%20packages%0Aimport%20geopandas%20as%20gpd%0Aimport%20matplotlib.pyplot%20as%20plt%0Afrom%20descartes%20import%20PolygonPatch%0Aimport%20shapefile%0Aimport%20cartopy.crs%20as%20ccrs%0Aimport%20cartopy.feature%20as%20cfeature%0Afrom%20matplotlib.offsetbox%20import%20AnchoredText” message=”” highlight=”” provider=”manual”/]

Step two

Load up your NUTS shapefile (obtain from eurostat)

[pastacode lang=”python” manual=”%23%20read%20the%20NUTS%20shapefile%20and%20extract%20the%20polygons%20for%20a%20individual%20countries%0Anuts%3Dgpd.read_file(‘%2Fpath%2Fto%2Fyour%2Fshapefiles%2FNUTS_RG_10M_2016_4326_LEVL_2.shp’)” message=”” highlight=”” provider=”manual”/]

Step three

The next piece of code defines a central point within each shape for labelling use

[pastacode lang=”python” manual=”%23%20use%20representitive_point%20method%20to%20obtain%20point%20from%20within%20each%20shapefile%0A%23%20creates%20new%20column%20’coords’%20containing%20the%20representitive%20points%0Anuts%5B’coords’%5D%20%3D%20nuts%5B’geometry’%5D.apply(lambda%20x%3A%20x.representative_point().coords%5B%3A%5D)%0Anuts%5B’coords’%5D%20%3D%20%5Bcoords%5B0%5D%20for%20coords%20in%20nuts%5B’coords’%5D%5D” message=”” highlight=”” provider=”manual”/]

Step four

Using matplotlib, plot the map.

[pastacode lang=”python” manual=”sf%3Dshapefile.Reader(%22%2FUsers%2Fuser%2FDocuments%2FC3S_Energy_Docs%2FNUTS2%2Fref-nuts-2016-10m.shp%2FNUTS_RG_10M_2016_4326_LEVL_2.shp%22)%0Adef%20main()%3A%0A%20%20%20%20fig%20%3D%20plt.figure(figsize%3D(80%2C40))%20%0A%20%20%20%20%0A%20%20%20%20ax%20%3D%20fig.add_subplot(1%2C%201%2C%201%2C%20projection%3Dccrs.PlateCarree())%0A%20%20%20%20%0A%20%20%20%20for%20poly%20in%20sf.shapes()%3A%0A%20%20%20%20%20%20%20%20poly_geo%3Dpoly.__geo_interface__%0A%20%20%20%20%20%20%20%20ax.add_patch(PolygonPatch(poly_geo%2C%20fc%3D’%23ffffff’%2C%20ec%3D’%23000000’%2C%20alpha%3D0.5%2C%20zorder%3D2%20))%0A%20%20%20%20ax.axis(‘scaled’)%0A%0A%0Aif%20__name__%20%3D%3D%20’__main__’%3A%0A%20%20%20%20main()” message=”” highlight=”” provider=”manual”/]

And that’s it, we’ve plotted the NUTS2 regions!

But as you can see, it still needs some tweaking to make the regions clearer for the user.

Step five

Let’s first change the map x,y limits to focus on the EU domain.

[pastacode lang=”python” manual=”sf%3Dshapefile.Reader(%22%2FUsers%2Fuser%2FDocuments%2FC3S_Energy_Docs%2FNUTS2%2Fref-nuts-2016-10m.shp%2FNUTS_RG_10M_2016_4326_LEVL_2.shp%22)%0Adef%20main()%3A%0A%20%20%20%20fig%20%3D%20plt.figure(figsize%3D(80%2C40))%20%0A%20%20%20%20%0A%20%20%20%20ax%20%3D%20fig.add_subplot(1%2C%201%2C%201%2C%20projection%3Dccrs.PlateCarree())%0A%20%20%20%20%0A%20%20%20%20for%20poly%20in%20sf.shapes()%3A%0A%20%20%20%20%20%20%20%20poly_geo%3Dpoly.__geo_interface__%0A%20%20%20%20%20%20%20%20ax.add_patch(PolygonPatch(poly_geo%2C%20fc%3D’%23ffffff’%2C%20ec%3D’%23000000’%2C%20alpha%3D0.5%2C%20zorder%3D2%20))%0A%20%20%20%20ax.axis(‘scaled’)%0A%20%20%20%20%0A%20%20%20%20%23%20adjust%20plot%20domain%20to%20focus%20on%20EU%20region%0A%20%20%20%20plt.xlim(-25%2C%2047)%0A%20%20%20%20plt.ylim(30%2C%2073)%20%20%20%0A%0Aif%20__name__%20%3D%3D%20’__main__’%3A%0A%20%20%20%20main()” message=”” highlight=”” provider=”manual”/]

Step six

Then Matplotlib can call the open source natural earth map resources for use as map elements (such as land/sea areas etc).

[pastacode lang=”python” manual=”sf%3Dshapefile.Reader(%22%2FUsers%2Fuser%2FDocuments%2FC3S_Energy_Docs%2FNUTS2%2Fref-nuts-2016-10m.shp%2FNUTS_RG_10M_2016_4326_LEVL_2.shp%22)%0Adef%20main()%3A%0A%20%20%20%20fig%20%3D%20plt.figure(figsize%3D(80%2C40))%20%0A%20%20%20%20%0A%20%20%20%20ax%20%3D%20fig.add_subplot(1%2C%201%2C%201%2C%20projection%3Dccrs.PlateCarree())%0A%20%20%20%20%23hi%20res%20polygons%20for%20land%2C%20sea%20etc%0A%20%20%20%20ax.coastlines(resolution%3D’10m’%2C%20color%3D’black’%2C%20linewidth%3D1)%0A%20%20%20%20ax.natural_earth_shp(name%3D’land’%2C%20resolution%3D’10m’%2C%20category%3D’physical’)%0A%20%20%20%20ax.natural_earth_shp(name%3D’ocean’%2C%20resolution%3D’50m’%2C%20category%3D’physical’%2Cfacecolor%3D’lightblue’)%0A%20%20%20%20%0A%20%20%20%20for%20poly%20in%20sf.shapes()%3A%0A%20%20%20%20%20%20%20%20poly_geo%3Dpoly.__geo_interface__%0A%20%20%20%20%20%20%20%20ax.add_patch(PolygonPatch(poly_geo%2C%20fc%3D’%23ffffff’%2C%20ec%3D’%23000000’%2C%20alpha%3D0.5%2C%20zorder%3D2%20))%0A%20%20%20%20ax.axis(‘scaled’)%0A%20%20%20%20%0A%20%20%20%20%23%20adjust%20plot%20domain%20to%20focus%20on%20EU%20region%0A%20%20%20%20plt.xlim(-25%2C%2047)%0A%20%20%20%20plt.ylim(30%2C%2073)%20%20%20%0A%0Aif%20__name__%20%3D%3D%20’__main__’%3A%0A%20%20%20%20main()” message=”” highlight=”” provider=”manual”/]

Step seven

Finally, looping through the central points in the shapefiles as defined earlier, gives us the labelling for each NUTS2 area.

[pastacode lang=”python” manual=”sf%3Dshapefile.Reader(%22%2Fpath%2Fto%2Fyour%2Fshapefiles%2Fref-nuts-2016-10m.shp%2FNUTS_RG_10M_2016_4326_LEVL_2.shp%22)%0Adef%20main()%3A%0A%20%20%20%20fig%20%3D%20plt.figure(figsize%3D(80%2C40))%20%0A%20%20%20%20%0A%20%20%20%20ax%20%3D%20fig.add_subplot(1%2C%201%2C%201%2C%20projection%3Dccrs.PlateCarree())%0A%20%20%20%20%0A%20%20%20%20%23hi%20res%20polygons%20for%20land%2C%20sea%20etc%0A%20%20%20%20ax.coastlines(resolution%3D’10m’%2C%20color%3D’black’%2C%20linewidth%3D1)%0A%20%20%20%20ax.natural_earth_shp(name%3D’land’%2C%20resolution%3D’10m’%2C%20category%3D’physical’)%0A%20%20%20%20ax.natural_earth_shp(name%3D’ocean’%2C%20resolution%3D’50m’%2C%20category%3D’physical’%2Cfacecolor%3D’lightblue’)%0A%0A%20%20%20%20%0A%20%20%20%20for%20poly%20in%20sf.shapes()%3A%0A%20%20%20%20%20%20%20%20poly_geo%3Dpoly.__geo_interface__%0A%20%20%20%20%20%20%20%20ax.add_patch(PolygonPatch(poly_geo%2C%20fc%3D’%23ffffff’%2C%20ec%3D’%23000000’%2C%20alpha%3D0.5%2C%20zorder%3D2%20))%0A%20%20%20%20ax.axis(‘scaled’)%0A%0A%20%20%20%20%23%20annotate%20plot%20with%20NUTS_ID%20%0A%20%20%20%20for%20idx%2C%20row%20in%20nuts.iterrows()%3A%0A%20%20%20%20%20%20%20%20plt.annotate(s%3Drow%5B’NUTS_ID’%5D%2C%20xy%3Drow%5B’coords’%5D%2C%20horizontalalignment%3D’center’)%0A%0A%20%20%20%20%23%20adjust%20plot%20domain%20to%20focus%20on%20EU%20region%0A%20%20%20%20plt.xlim(-25%2C%2047)%0A%20%20%20%20plt.ylim(30%2C%2073)%20%20%20%20%20%0A%0A%20%20%20%20%23%20Add%20a%20text%20annotation%20for%20the%20license%20information%20to%20the%0A%20%20%20%20%23%20the%20bottom%20right%20corner.%0A%20%20%20%20text%20%3D%20AnchoredText(r’%24%5Cmathcircled%7B%7Bc%7D%7D%24%20Luke%20Sanger%20-%20WEMC%20(2020)’%2Cloc%3D4%2C%20prop%3D%7B’size’%3A%2012%7D%2C%20frameon%3DTrue)%0A%20%20%20%20ax.add_artist(text)%0A%20%20%20%20plt.show()%0A%0Aif%20__name__%20%3D%3D%20’__main__’%3A%0A%20%20%20%20main()” message=”” highlight=”” provider=”manual”/]

There you have it.

We know that the London regions are quite densely clustered at this level and as a result, the labels are overlapping, so it’s not ideal. But this does demonstrate one way you can visualize NUTS2 Eurostat regions in the EU domain.

Luke Sanger (WEMC Data Engineer)

Recent Posts