import os, numpy as np, pandas as pd, cartopy.crs as ccrs, bokeh
import holoviews as hv, geoviews as gv, datashader as ds, panel as pn
from holoviews.operation.datashader import rasterize, datashade
from colorcet import bmy, bgyw, isolum
from holoviews.util import Dynamic
from bokeh.models import HoverTool, CustomJSHover
hv.extension('bokeh', width=100)
pn.extension()
from version import __version__
data = pd.read_hdf('./data/rgi62_era5_itmix_df.h5', 'df')
data = data.rename(columns={'Area':'area'})
data['latdeg'] = data.CenLat
data['vol_asl_m3'] = data.vol_itmix_m3 - data.vol_bsl_itmix_m3
data['vol_itmix_km3'] = data.vol_itmix_m3 * 1e-9
data.tail()
total_area = data.area.sum()
total_vol = data.vol_itmix_km3.sum()
data = gv.Points(data, [('CenLon', 'Longitude'), ('CenLat', 'Latitude')],
[('era5_avg_pcp', 'Annual Precipitation (mm/yr)'),
('area', 'Area'), ('latdeg', 'Latitude (deg)'),
('era5_avg_temp_at_zmed', 'Annual Temperature at avg. altitude (Ā°C)'),
('Zmed', 'Mean elevation of the glacier (m a.s.l.)'), ('vol_asl_m3', 'Volume asl'),
('vol_bsl_itmix_m3', 'Volume bsl'),
('vol_itmix_km3', 'Volume km3'),
('era5_trend', 'Temperature trend 1979-2018 (Ā°C per decade)')])
data = gv.Dataset(gv.operation.project_points(data))
def compute_slr(ice_vol_m3):
"""ice_vol in mĀ³ gives slr in mm"""
rho = 900
rho_oc = 1028
A_oc = 362.5 * 1e9
return ice_vol_m3 * rho / (A_oc * rho_oc)
total_slr = compute_slr(data['vol_asl_m3']).sum()
np.testing.assert_allclose(total_slr, 324.3, atol=0.5) # somethings slightly different...
# Datashader map
geo_kw = dict(aggregator=ds.sum('area'), x_sampling=1000, y_sampling=1000)
# Elev vs Lat scatter
elev_kw = dict(cmap='#7d3c98')
# Histograms
temp_kw = dict(num_bins=50, adjoin=False, normed=False, bin_range=data.range('era5_avg_temp_at_zmed'))
prcp_kw = dict(num_bins=50, adjoin=False, normed=False, bin_range=(0, 6000)) # for precip we crop large values
tren_kw = dict(num_bins=50, adjoin=False, normed=False, bin_range=data.range('era5_trend'))
size_opts_map = dict(width=715, height=520)
size_opts_his = dict(height=180, width=350)
size_opts_bar = dict(height=45, width=250)
size_opts_slr = dict(height=300, width=120)
size_text_bar = dict(height=20)
geo_opts = dict(size_opts_map, cmap=bmy, global_extent=True, logz=True, colorbar=True, colorbar_opts={'title':'Area (kmĀ²)'},
toolbar='above', projection=ccrs.GOOGLE_MERCATOR)
elev_opts = dict(size_opts_his, show_grid=True)
temp_opts = dict(size_opts_his, fill_color='#f1948a', default_tools=[], toolbar=None, ylabel='', alpha=1.0)
prcp_opts = dict(size_opts_his, fill_color='#85c1e9', default_tools=[], toolbar=None, ylabel='', alpha=1.0)
tren_opts = dict(size_opts_his, fill_color='#f4d34e', default_tools=[], toolbar=None, alpha=1.0)
slr_opts = dict(size_opts_slr, color='orange', default_tools=[], toolbar=None, xlabel='', show_legend=False, yticks=[0, 50, 100, 150,200,250,300,350,390], shared_axes=False)
glno_opts = dict(size_opts_bar, color='#326a86', default_tools=[], toolbar=None, alpha=0.8, invert_axes=True, show_legend=False, xaxis=None, yaxis=None, shared_axes=False)
area_opts = dict(size_opts_bar, color='#326a86', default_tools=[], toolbar=None, alpha=0.8, invert_axes=True, show_legend=False, xaxis=None, yaxis=None, shared_axes=False)
vol_opts = dict(size_opts_bar, color='#326a86', default_tools=[], toolbar=None, alpha=0.8, invert_axes=True, show_legend=False, xaxis=None, yaxis=None, shared_axes=False)
from international import trads, supported_languages
language = 'en'
def base_slr(data):
return hv.Bars([('asl', compute_slr(np.sum(data['vol_asl_m3']))), ('bsl', compute_slr(np.sum(data['vol_bsl_itmix_m3'])))])
static_slr = base_slr(data)
gl_number = len(data['area'])
def geo(data):
return gv.Points(data, crs=ccrs.PlateCarree).options(alpha=1)
def elev(data):
return data.to(hv.Scatter, 'Zmed', 'latdeg', [])
def temp(data):
return data.hist('era5_avg_temp_at_zmed', **temp_kw).options(**temp_opts)
def prcp(data):
return data.hist('era5_avg_pcp', **prcp_kw).options(**prcp_opts)
def tren(data):
return data.hist('era5_trend', **tren_kw).options(**tren_opts)
def slr(data):
slr_opts['ylabel'] = trads['bar_sealevel_y'][language]
return static_slr.opts(**slr_opts, alpha=0.1) * base_slr(data).opts(**slr_opts)
def gl_no(data):
return hv.Bars(('', len(data))).opts(**glno_opts)
def area(data):
return hv.Bars(('', np.sum(data['area']))).opts(**area_opts)
def vol(data):
return hv.Bars(('', np.sum(data['vol_itmix_km3']))).opts(**vol_opts)
def count1(data):
legend = trads['bar_glaciers_selected'][language]
text = '<p style="margin-top: 0px; font-size:15px;">{}</font>'
v1, v2 = len(data), gl_number
if trads['is_rtl'].get(language, False):
return hv.Div(text.format(legend).format(v2, v1)).options(**size_text_bar)
else:
return hv.Div(text.format(legend).format(v1, v2)).options(**size_text_bar)
def count2(data):
legend = trads['bar_area'][language]
v1, v2 = np.sum(data['area']), np.sum(data['area']) / total_area * 100
if trads['is_rtl'].get(language, False):
text = '<p style="margin-top: 0px;">({:.1f}%) {:.0f} kmĀ² :{}</p>'
return hv.Div(text.format(v2, v1, legend)).options(**size_text_bar)
else:
text = '<p style="margin-top: 0px;">{}: {:.0f} kmĀ² ({:.1f}%)</p>'
return hv.Div(text.format(legend, v1, v2)).options(**size_text_bar)
def count3(data):
legend = trads['bar_volume'][language]
v1, v2 = np.sum(data['vol_itmix_km3']), np.sum(data['vol_itmix_km3']) / total_vol * 100
if trads['is_rtl'].get(language, False):
text = '<p style="margin-top: 0px;">({:.1f}%) {:.0f} kmĀ³ :{}</p>'
return hv.Div(text.format(v2, v1, legend)).options(**size_text_bar)
else:
text = '<p style="margin-top: 0px;">{}: {:.0f} kmĀ³ ({:.1f}%)</p>'
return hv.Div(text.format(legend, v1, v2)).options(**size_text_bar)
def slr_text(data):
legend = trads['bar_sealevel_text'][language]
v1, v2 = compute_slr(np.sum(data['vol_asl_m3'])), compute_slr(np.sum(data['vol_asl_m3'])) / total_slr * 100
if trads['is_rtl'].get(language, False):
text = '<p style="margin-top: 0px;">({:.1f}%) {:.1f} mm {}</p>'
return hv.Div(text.format(v2, v1, legend)).options(**size_text_bar)
else:
text = '<p style="margin-top: 0px;">{}{:.1f} mm ({:.1f}%)</p>'
return hv.Div(text.format(legend, v1, v2)).options(**size_text_bar)
static_geo = rasterize(geo(data), **geo_kw).options(alpha=0.1, tools=['hover', 'box_select'], active_tools=['box_select'], **geo_opts)
static_elev = datashade(elev(data), **elev_kw).options(alpha=0.1, tools=[ 'box_select'], active_tools=['box_select'], toolbar=None, **elev_opts)
static_gl_no= gl_no(data).options(alpha=0.1)
static_area = area(data).options(alpha=0.1)
static_vol = vol(data).options(alpha=0.1)
static_tren = tren(data).options(alpha=0.1)
static_temp = temp(data).options(alpha=0.1)
static_prcp = prcp(data).options(alpha=0.1)
def combine_selections(**kwargs):
"""
Combines selections on all available plots into a single selection by index.
"""
if all(not v for v in kwargs.values()):
return slice(None)
selection = {}
for key, bounds in kwargs.items():
if bounds is None:
continue
elif len(bounds) == 2:
selection[key] = bounds
else:
xbound, ybound = key.split('__')
selection[xbound] = bounds[0], bounds[2]
selection[ybound] = bounds[1], bounds[3]
return sorted(set(data.select(**selection).data.index))
def select_data(**kwargs):
return data.iloc[combine_selections(**kwargs)] if kwargs else data
def clear_selections(arg=None):
geo_bounds.update(bounds=None)
elev_bounds.update(bounds=None)
temp_bounds.update(boundsx=None)
prcp_bounds.update(boundsx=None)
tren_bounds.update(boundsx=None)
Stream.trigger(selections)
from holoviews.streams import Stream, BoundsXY, BoundsX
geo_bounds = BoundsXY(source=static_geo, rename= {'bounds': 'CenLon__CenLat'})
elev_bounds = BoundsXY(source=static_elev, rename= {'bounds': 'Zmed__latdeg'})
temp_bounds = BoundsX( source=static_temp, rename= {'boundsx': 'era5_avg_temp_at_zmed'})
prcp_bounds = BoundsX( source=static_prcp, rename= {'boundsx': 'era5_avg_pcp'})
tren_bounds = BoundsX( source= static_tren, rename= {'boundsx': 'era5_trend'})
selections = [geo_bounds, elev_bounds, temp_bounds, prcp_bounds, tren_bounds]
dyn_data = hv.DynamicMap(select_data, streams=selections)
dyn_geo = rasterize(dyn_data.apply(geo), **geo_kw).options( **geo_opts)
dyn_elev = datashade(dyn_data.apply(elev), **elev_kw).options(**elev_opts)
dyn_temp = dyn_data.apply(temp)
dyn_prcp = dyn_data.apply(prcp)
dyn_count1= dyn_data.apply(count1)
dyn_count2= dyn_data.apply(count2)
dyn_count3= dyn_data.apply(count3)
dyn_slr_text = dyn_data.apply(slr_text)
dyn_tren = dyn_data.apply(tren)
dyn_slr = dyn_data.apply(slr)
dyn_gl_no = dyn_data.apply(gl_no)
dyn_area = dyn_data.apply(area)
dyn_vol = dyn_data.apply(vol)
geo_bg = gv.tile_sources.EsriImagery.options(alpha=1.0, bgcolor="white")
geomap = geo_bg * static_geo * dyn_geo
elevation = static_elev * dyn_elev
temperature = static_temp * dyn_temp
precipitation = static_prcp * dyn_prcp
trends = static_tren * dyn_tren
sealevelrise = dyn_slr
gl_num = static_gl_no * dyn_gl_no
area_bar = static_area * dyn_area
vol_bar = static_vol * dyn_vol
# set range
elevation = elevation.redim(latdeg=dict(range=(-90, 90)), Zmed=dict(range=(0, 8000)))
def set_button_name_language():
clear_button.name = trads['clear_button'][language]
clear_button = pn.widgets.Button(name='', width=170)
clear_button.param.watch(clear_selections, 'clicks')
set_button_name_language()
def language_selector_function(arg=None):
global language
# Change back display to selector
language = language_selector.value
for k, v in trads['lang_display'].items():
language = language.replace(v, k)
change_language()
def change_hist_label_language():
temp_opts['xlabel'] = trads['temp_plot_x'][language]
prcp_opts['xlabel'] = trads['precip_plot_x'][language]
tren_opts['xlabel'] = trads['trend_plot_x'][language]
tren_opts['ylabel'] = trads['trend_plot_y'][language]
def change_language(arg=None):
set_instr_text()
set_explanation_text()
change_hist_label_language()
clear_selections()
set_button_name_language()
def replace_lan(l):
# Change selector to display characters
for k, v in trads['lang_display'].items():
l = l.replace(k, v)
return l
language_selector = pn.widgets.RadioBoxGroup(name='Select your language',
options=[replace_lan(l) for l in supported_languages],
inline=True,
margin=(0, 0),
width=540,
# background='red'
)
language_selector.param.watch(language_selector_function, 'value');
Here you find a short introduction how to adapt the width of the language_selector if a new language is added. This is needed because the the RadioBosGroup is not adapting the width automatically to the content (see https://github.com/holoviz/panel/issues/3533).
background='red'
in the cell above and execute cellwidth
so that all languages are on the red background (include a little extra space to the right)# background='red'
app.show()
to open in new tab)def elev_language(plot, element):
plot.handles['xaxis'].axis_label = trads['elev_plot_x'][language]
plot.handles['yaxis'].axis_label = trads['elev_plot_y'][language]
def geo_language(plot, element):
plot.handles['xaxis'].axis_label = trads['map_plot_x'][language]
plot.handles['yaxis'].axis_label = trads['map_plot_y'][language]
# changing the title of the colorbar, caution if one day the colorbar is not located on the right side anymore
plot.state.right[0].title = trads['bar_area'][language] + ' (kmĀ²)'
oggm_logo = '<p style="margin-top: 0px;margin-bottom: 0px;"><a href="https://edu.oggm.org"><img src="https://raw.githubusercontent.com/OGGM/world-glacier-explorer/master/img/logo_edu.png" width=180 height=79></a></p>'
fk_logo = '<p style="margin-top: 0px;margin-bottom: 0px;"><a href="https://www.uibk.ac.at/foerderkreis1669/"><img src="https://raw.githubusercontent.com/OGGM/world-glacier-explorer/master/img/logo_1669.png" width=180 height=79></a></p>'
unib_logo = '<p style="margin-top: 0px;margin-bottom: 0px;"><a href="https://www.uni-bremen.de/"><img src="https://raw.githubusercontent.com/OGGM/world-glacier-explorer/master/img/logo_uni_bremen.png" width=130></a></p>'
pn_logo = '<p style="margin-top: 0px;margin-bottom: 0px;"><a href="https://panel.pyviz.org"><img src="https://panel.pyviz.org/_static/logo_stacked.png" width=46 height=39></a></p>'
holo_logo = '<p style="margin-top: 0px;margin-bottom: 0px;"><a href="https://holoviz.org/"><img src="https://raw.githubusercontent.com/pyviz/holoviews/master/doc/_static/logo.png" width=46 height=39></a></p>'
dasha_logo = '<p style="margin-top: 0px;margin-bottom: 0px;"><a href="https://datashader.org/"><img src="https://raw.githubusercontent.com/pyviz/datashader/master/doc/_static/datashader-logo.png" width=46 height=39></a></p>'
logos = pn.Row(pn.layout.Spacer(width=10), pn_logo, holo_logo, dasha_logo)
logos_height_spacer = 25
left = pn.Column(pn.Pane(oggm_logo, margin=(0, 0), align='center'), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),
pn.Pane(logos, margin=(0, 0), align='center'), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),
pn.Pane(fk_logo, margin=(0, 0), align='center'), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),
pn.Pane(unib_logo, margin=(0, 0), align='center', height=50), pn.layout.Spacer(height=logos_height_spacer, margin=(0, 0)),
clear_button)
bars = pn.Row(pn.Column(pn.layout.Spacer(height=1, margin=(0, 0)),
pn.Pane(dyn_count1), pn.layout.Spacer(height=0, margin=(0, 0)),
pn.Pane(gl_num, linked_axes=False, margin=(0, 0)), pn.layout.Spacer(sizing_mode='stretch_height', margin=(0, 0)),
pn.Pane(dyn_count2), pn.layout.Spacer(height=0, margin=(0, 0)),
pn.Pane(area_bar, linked_axes=False, margin=(0, 0)), pn.layout.Spacer(sizing_mode='stretch_height', margin=(0, 0)),
pn.Pane(dyn_count3), pn.layout.Spacer(height=0, margin=(0, 0)),
pn.Pane(vol_bar, linked_axes=False, margin=(0, 0)), pn.layout.Spacer(sizing_mode='stretch_height', margin=(0, 0)),
pn.Pane(dyn_slr_text), pn.layout.Spacer(height=3, margin=(0, 0)),
),
pn.Column(pn.layout.Spacer(height=5, margin=(0, 0)),
pn.Pane(sealevelrise, linked_axes=False, margin=(0, 0)),
pn.layout.Spacer(height=5, margin=(0, 0)))
)
title = '<div style="font-size:35px">World glaciers explorer</div>'
instruction = pn.pane.Markdown(sizing_mode='stretch_width', height=100)
def set_instr_text():
instruction.object = '<p style="margin-top: 0px;">' + trads['instructions'][language] + '</p>'
set_instr_text()
explanation = pn.pane.Markdown(sizing_mode='stretch_width', margin=(0, 0), height=60)
def set_explanation_text():
explanation.object = '<p style="margin-top: 0px;">' + trads['abbreviations'][language] + '</p>'
set_explanation_text()
overview = pn.Column(pn.Pane(title, width=400, margin=(0, 0)),
pn.layout.Spacer(height=1, margin=(0, 0)),
pn.Pane(instruction, width=470, margin=(0, 0)),
pn.layout.Spacer(height=10, margin=(0, 0)),
bars)
plots = pn.Row(pn.layout.Spacer(width=10, margin=(0, 0)),
trends, pn.layout.Spacer(sizing_mode='stretch_width', margin=(0, 0)),
temperature, pn.layout.Spacer(sizing_mode='stretch_width', margin=(0, 0)),
precipitation, pn.layout.Spacer(sizing_mode='stretch_width', margin=(0, 0)), elevation.options(hooks=[elev_language]),
pn.layout.Spacer(width=10, margin=(0, 0)),
sizing_mode='stretch_width')
top = pn.Row(left, pn.Spacer(width=60, margin=(0, 0)),
overview, pn.Spacer(width=60, margin=(0, 0)),
geomap.options(hooks=[geo_language]),
margin=(0, 0))
app = pn.Column(pn.Row(pn.Spacer(sizing_mode='stretch_width',
margin=(0, 0)),
language_selector,
sizing_mode='stretch_width'),
top,
plots,
explanation,
width=1465,
)
app.servable(title='World glaciers explorer ' + __version__)