In [1]:
%matplotlib inline

import pandas as pd
import folium
from ipywidgets import HTML

Data exploration map for ICPW

Switching to Folium as Google Fusion Tables has been deprecated.

1. Read data


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]:
project_id project_name station_id station_code station_name nfc_code type continent country region ... par_id non_missing median std_dev mk_p_val trend sen_slp rel_sen_slp include link
0 4390 ICPW_TOCTRENDS_2018 38085 Tr18_CA01 Ontario, Algoma Region, Batchawana Lake NaN L NaN Canada ONT ... Al 27 104.000000 19.108938 7.074787e-01 no trend 0.121250 0.001166 yes http://77.104.141.195/~icpwater/wp-content/cor...
1 4390 ICPW_TOCTRENDS_2018 38085 Tr18_CA01 Ontario, Algoma Region, Batchawana Lake NaN L NaN Canada ONT ... TOC 27 4.668000 0.319514 3.281559e-03 increasing 0.019687 0.004218 yes http://77.104.141.195/~icpwater/wp-content/cor...
2 4390 ICPW_TOCTRENDS_2018 38085 Tr18_CA01 Ontario, Algoma Region, Batchawana Lake NaN L NaN Canada ONT ... EH 27 1.023293 0.251578 4.399443e-01 no trend -0.005823 -0.005691 yes http://77.104.141.195/~icpwater/wp-content/cor...
3 4390 ICPW_TOCTRENDS_2018 38085 Tr18_CA01 Ontario, Algoma Region, Batchawana Lake NaN L NaN Canada ONT ... ESO4 27 95.000000 20.025680 1.926214e-09 decreasing -2.333912 -0.024567 yes http://77.104.141.195/~icpwater/wp-content/cor...
4 4390 ICPW_TOCTRENDS_2018 38085 Tr18_CA01 Ontario, Algoma Region, Batchawana Lake NaN L NaN Canada ONT ... ECl 27 7.142857 1.951420 4.931664e-03 decreasing -0.155429 -0.021760 yes http://77.104.141.195/~icpwater/wp-content/cor...

5 rows × 25 columns

2. Build map

2.1. Legend code

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)

2.2. HTML for popups


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>
"""

2.3. Create map


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")

3. Modify JavaScript

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);