In [1]:
%matplotlib inline
import pandas as pd
import folium
from ipywidgets import HTML
In [2]:
df = pd.read_csv('toc_trends_long_format.csv')
df.dropna(subset=['latitude', 'longitude'], inplace=True)
df = df.query('(analysis_period == "1990-2016") and (non_missing > 0)')
base = "http://77.104.141.195/~icpwater/wp-content/core_plots/trends_plots_1990-2016/"
fname = df['station_id'].astype(str) + '_' + df['par_id'] + '_' + df['data_period'] + '.png'
df['link'] = base + fname
df.head()
Out[2]:
The code below for adding a legend is taken from:
https://github.com/python-visualization/folium/issues/528#issuecomment-421445303
In [3]:
from branca.element import Template, MacroElement
template = """
{% macro html(this, kwargs) %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Draggable - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( "#maplegend" ).draggable({
start: function (event, ui) {
$(this).css({
right: "auto",
top: "auto",
bottom: "auto"
});
}
});
});
</script>
</head>
<body>
<div id='maplegend' class='maplegend'
style='position: absolute; z-index:9999; border:2px solid grey; background-color:rgba(255, 255, 255, 0.8);
border-radius:6px; padding: 10px; font-size:14px; right: 20px; bottom: 20px;'>
<div class='legend-title'>Legend (draggable)</div>
<div class='legend-scale'>
<ul class='legend-labels'>
<li><span style='background:red;opacity:0.7;'></span>Increasing</li>
<li><span style='background:yellow;opacity:0.7;'></span>No trend</li>
<li><span style='background:green;opacity:0.7;'></span>Decreasing</li>
</ul>
</div>
</div>
</body>
</html>
<style type='text/css'>
.maplegend .legend-title {
text-align: left;
margin-bottom: 5px;
font-weight: bold;
font-size: 90%;
}
.maplegend .legend-scale ul {
margin: 0;
margin-bottom: 5px;
padding: 0;
float: left;
list-style: none;
}
.maplegend .legend-scale ul li {
font-size: 80%;
list-style: none;
margin-left: 0;
line-height: 18px;
margin-bottom: 2px;
}
.maplegend ul.legend-labels li span {
display: block;
float: left;
height: 16px;
width: 30px;
margin-right: 5px;
margin-left: 0;
border: 1px solid #999;
}
.maplegend .legend-source {
font-size: 80%;
color: #777;
clear: both;
}
.maplegend a {
color: #777;
}
</style>
{% endmacro %}"""
macro = MacroElement()
macro._template = Template(template)
In [4]:
# HTML for popup styling
html = """
<center><h3>{par_id} at {station_name}, {country}</h3></center>
<center><table>
<tr>
<td><b>ICPW ID:</b></td>
<td>{station_id}</td>
</tr>
<tr>
<td><b>ICPW code:</b></td>
<td>{station_code}</td>
</tr>
<tr>
<td><b>NFC code:</b></td>
<td>{nfc_code}</td>
</tr>
<tr>
<td><b>Number of years with data:</b></td>
<td>{non_missing}</td>
</tr>
<tr>
<td><b>Median:</b></td>
<td>{median:.3f}</td>
</tr>
<tr>
<td><b>Standard deviation:</b></td>
<td>{std_dev:.3f}</td>
</tr>
<tr>
<td><b>Mann-Kendall p-value:</b></td>
<td>{mk_p_val:.3f}</td>
</tr>
<tr>
<td><b>Trend:</b></td>
<td>{trend}</td>
</tr>
<tr>
<td><b>Theil-Sen slope:</b></td>
<td>{sen_slp:.3f}</td>
</tr>
</table></center>
<center><img src={link} height="300"></center>
"""
In [5]:
# Create basemap
m = folium.Map(location=[55, -35], zoom_start=3)
# Add Google aerial imagery
folium.raster_layers.TileLayer(tiles='http://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
attr='google',
name='Google satellite',
max_zoom=20,
subdomains=['mt0', 'mt1', 'mt2', 'mt3'],
overlay=False,
control=True).add_to(m)
# Loop over parameters
for par in df['par_id'].unique():
# Add feature group. Show TOC by default
if par == 'TOC':
fg = folium.FeatureGroup(name=par, show=True)
else:
fg = folium.FeatureGroup(name=par, show=False)
m.add_child(fg)
# Get data
par_df = df.query('par_id == @par')
rec_list = par_df.to_dict(orient='records')
# Get station summary data
for rec in rec_list:
popup = HTML(html.format(**rec))
# Get marker colour
trend = rec['trend']
if trend == 'increasing':
colour = 'red'
elif trend == 'decreasing':
colour = 'green'
else:
colour = 'yellow'
cm = folium.CircleMarker(location=[rec['latitude'], rec['longitude']],
radius=6,
popup=popup,
parse_html=True,
fill=True,
fill_color=colour,
color='black',
fill_opacity=0.7,
)
fg.add_child(cm)
folium.LayerControl().add_to(m)
m.get_root().add_child(macro)
m.save("icpw_map.html")
The code above creates a map where each parameter is added as an "overlay", which means it is possible to select more than one layer at a time. This is undesirable, but Folium doesn't seem to allow adding mutually exclusive feature groups. (It is possible to se overlay = False in folium.FeatureGroup, but then the layers appear together with the basemaps, so users can have either a data layer or a map background, but not both.
A workaround is possible by manually hacking the JavaScript, based on this post. To do this, make a copy of icpw_map.html and open it for editing. Scroll to the end of the file and replace
L.control.layers(
layer_control_7c0dcd15fbc843c58cc7d46dc9a79085.base_layers,
layer_control_7c0dcd15fbc843c58cc7d46dc9a79085.overlays,
{"autoZIndex": true, "collapsed": true, "position": "topright"}
).addTo(map_d16809f428894627ace203a60fb181d9);
with
L.control.layers(
layer_control_7c0dcd15fbc843c58cc7d46dc9a79085.base_layers,
).addTo(map_d16809f428894627ace203a60fb181d9);
L.control.layers(
layer_control_7c0dcd15fbc843c58cc7d46dc9a79085.overlays,
).addTo(map_d16809f428894627ace203a60fb181d9);