python4oceanographers

Turning ripples into waves

Plotting a GeoDataFrame with folium

The visualization of thematic maps can get very messy very quick when there are many points to plot display.

In this post we will plot data from shapefile in the most visually efficient way possible.

Let's open our shapefiles with geopandas. (GeoPandas makes our task easy and that will be clear in a moment.)

In [3]:
import geopandas

path = './data/energy/{}'.format
hidro = geopandas.GeoDataFrame.from_file(path('ENC_Hidreletrica_P.shp'))
termo = geopandas.GeoDataFrame.from_file(path('ENC_Termeletrica_P.shp'))

We need to know the Coordinate Reference System (CRS) of the data. That information is stored in the .crs property and we can use pyepsg to get more information about it.

In [4]:
import pyepsg

pyepsg.get(hidro.crs['init'].split(':')[1])
Out[4]:
<GeodeticCRS: 4674, SIRGAS 2000>

SIRGAS 2000 is a common CRS used both onshore and offshore data in Central America and South America. However, to plot the data on a folium map, we need to convert to a Geographic coordinate system with the wgs84 datum (EPSG: 4326). We also need to greate a GeoJSON object out of the GeoDataFrame.

Luckily, geopandas makes that extremely easy with the to_crs() method and, chained with the to_json(), we have an object ready for plotting with just one line.

In [5]:
gjson = hidro.to_crs(epsg='4326').to_json()
In [6]:
import folium

mapa = folium.Map([-15.783333, -47.866667],
                  zoom_start=4,
                  tiles='cartodbpositron')

points = folium.features.GeoJson(gjson)

mapa.add_children(points)
mapa
Out[6]:

It is very hard to make sense of what we are plotting. The information is cluttered and has no description.

To make it better we can add more information as rich HTML popups. And to reduced the clutter we can use the plugin MarkerCluster. Since we have to groups of data to plot we can also use FeatureGroup to turn on/off the display of a specific group.

In [7]:
table = """
<!DOCTYPE html>
<html>
<head>
<style>
table {{
    width:100%;
}}
table, th, td {{
    border: 1px solid black;
    border-collapse: collapse;
}}
th, td {{
    padding: 5px;
    text-align: left;
}}
table#t01 tr:nth-child(odd) {{
    background-color: #eee;
}}
table#t01 tr:nth-child(even) {{
   background-color:#fff;
}}
</style>
</head>
<body>

<table id="t01">
  <tr>
    <td>Type</td>
    <td>{}</td>
  </tr>
  <tr>
    <td>Name</td>
    <td>{}</td>
  </tr>
  <tr>
    <td>Operational</td>
    <td>{}</td>
  </tr>
</table>
</body>
</html>
""".format
In [8]:
mapa = folium.Map([-15.783333, -47.866667],
                  zoom_start=4,
                  tiles='cartodbpositron')
In [9]:
from folium.element import IFrame
from folium.plugins import MarkerCluster

width, height = 310,110
popups, locations = [], []
for idx, row in hidro.iterrows():
    locations.append([row['geometry'].y, row['geometry'].x])
    name = row['NOME'].encode('ascii', 'xmlcharrefreplace')
    if row['OPERACIONA'] == 'Sim':
        opr = 'Yes'
    elif row['OPERACIONA'] == u'Não':
        opr = 'No'
    else:
        opr = 'NA'
    iframe = IFrame(table('Hydroelectric', name, opr), width=width, height=height)
    popups.append(iframe)
    
h = folium.FeatureGroup(name='Hydroelectric')
h.add_children(MarkerCluster(locations=locations, popups=popups))
mapa.add_children(h)
In [10]:
popups, locations = [], []
for idx, row in termo.iterrows():
    locations.append([row['geometry'].y, row['geometry'].x])
    name = row['NOME'].encode('ascii', 'xmlcharrefreplace')
    if row['OPERACIONA'] == 'Sim':
        opr = 'Yes'
    elif row['OPERACIONA'] == u'Não':
        opr = 'No'
    else:
        opr = 'NA'
    iframe = IFrame(table('Thermoelectric', name, opr), width=width, height=height)
    popups.append(iframe)

t = folium.FeatureGroup(name='Thermoelectric')
t.add_children(MarkerCluster(locations=locations, popups=popups))
mapa.add_children(t)
In [11]:
mapa.add_children(folium.LayerControl())
mapa
Out[11]: