Skip to content

Commit

Permalink
Merge pull request #25 from oislen/dev
Browse files Browse the repository at this point in the history
23 map timeline controls
  • Loading branch information
oislen authored Oct 14, 2024
2 parents d5e64a1 + 453566c commit 8f0cda9
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 91 deletions.
5 changes: 5 additions & 0 deletions dashboard/bokeh_dash_app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# load relevant libraries
import logging
from bokeh.io import curdoc
from bokeh.models import TabPanel, Tabs

# load custom modules
from utilities.bokeh_line_dash import bokeh_line_dash
from utilities.bokeh_map_dash import bokeh_map_dash

# set up logging
lgr = logging.getLogger()
lgr.setLevel(logging.INFO)

# initialise and structure bokeh line dashboard
dashboard_line = bokeh_line_dash()
panel_line_tab = TabPanel(child = dashboard_line, title = 'Time Series')
Expand Down
1 change: 1 addition & 0 deletions dashboard/cons.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
linedash_year_timespan = ["2010", linedash_yearend]
linedash_yearmonth_timespan = ["2010-01", f"{linedash_yearend}-12"]
linedash_month_timespan = ["01", "12"]
linedash_year_options = [str(year) for year in range(int(linedash_year_timespan[0]), int(linedash_year_timespan[1])+1)]

# bokeh server execution commands
bat_execBokehApp = "START /MIN CMD.EXE /C exeBokehApp.bat"
Expand Down
27 changes: 12 additions & 15 deletions dashboard/utilities/bokeh_line_dash.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pickle
import logging
from beartype import beartype
from typing import Union
from bokeh.models import Select, CheckboxGroup, Div, Button, MultiSelect
Expand All @@ -8,6 +9,7 @@
import cons
from utilities.bokeh_line_data import bokeh_line_data
from utilities.bokeh_line_plot import bokeh_line_plot
from utilities.timeit import timeit

@beartype
def bokeh_line_dash():
Expand All @@ -21,21 +23,20 @@ def bokeh_line_dash():
bokeh.layouts.row
The interactive bokeh line dashboard
"""
logging.info("Initialise line plot begin")
with open(cons.preaggregate_data_fpath, "rb") as handle:
pre_agg_data_dict = pickle.load(handle)
# generate bokeh data for line plot
bokeh_line_data_dict = bokeh_line_data(pre_agg_data_dict)
bokeh_line_data_params = {"pre_agg_data_dict":pre_agg_data_dict}
bokeh_line_data_dict = timeit(func=bokeh_line_data, params=bokeh_line_data_params)
# create bokeh plot
line_plot = bokeh_line_plot(
bokeh_line_data_dict,
col=cons.col_default,
stat=cons.stat_default,
agg_level=cons.line_agg_level_default,
selection=cons.counties,
)
bokeh_line_plot_params = {"bokeh_data_dict":bokeh_line_data_dict, "col":cons.col_default, "stat":cons.stat_default, "agg_level":cons.line_agg_level_default, "selection":cons.counties}
line_plot = timeit(func=bokeh_line_plot, params=bokeh_line_plot_params)
logging.info("Initialise line plot end")

# create call back function for bokeh dashboard interaction
def callback_line_plot(attr, old, new):
logging.info("Callback line plot begin")
# extract new selector value
agg_level = line_agg_level_selector.value
col = line_col_selector.value
Expand All @@ -44,15 +45,11 @@ def callback_line_plot(attr, old, new):
for i in line_county_multiselect.value:
selection.append(cons.counties[int(i)])
# update bokeh plot
line_plot = bokeh_line_plot(
bokeh_line_data_dict,
col=col,
stat=stat,
agg_level=agg_level,
selection=selection,
)
bokeh_line_plot_params = {"bokeh_data_dict":bokeh_line_data_dict, "col":col, "stat":stat, "agg_level":agg_level, "selection":selection}
line_plot = timeit(func=bokeh_line_plot, params=bokeh_line_plot_params)
# reassign bokeh plot to bokeh dashboard
dashboard_line.children[1] = line_plot
logging.info("Callback line plot end")

def callback_multiselect_selectall():
line_county_multiselect.value = cons.counties_values
Expand Down
46 changes: 27 additions & 19 deletions dashboard/utilities/bokeh_map_dash.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import pickle
import logging
from bokeh.models import Select, Div, CheckboxButtonGroup
from bokeh.layouts import column, row
from beartype import beartype

import cons
from utilities.bokeh_map_data import bokeh_map_data
from utilities.bokeh_map_plot import bokeh_map_plot
from utilities.timeit import timeit

@beartype
def bokeh_map_dash():
Expand All @@ -19,37 +21,36 @@ def bokeh_map_dash():
bokeh.layouts.row
The interactive bokeh map dashboard
"""
logging.info("Initialise map plot begin")
with open(cons.map_data_fpath, "rb") as handle:
map_data_dict = pickle.load(handle)
map_data = pickle.load(handle)
with open(cons.points_data_fpath, "rb") as handle:
station_data = pickle.load(handle)
# generate bokeh data for map plot
bokeh_map_data_dict, pointgeosource = bokeh_map_data(map_data_dict, station_data)
bokeh_map_data_params = {"map_data":map_data,"station_data":station_data,"col":cons.col_default,"stat":cons.stat_default,"year":cons.linedash_year_timespan[1]}
bokeh_map_data_dict = timeit(func=bokeh_map_data, params=bokeh_map_data_params)
# create bokeh map plot
map_plot = bokeh_map_plot(
bokeh_map_data_dict=bokeh_map_data_dict,
pointgeosource=pointgeosource,
col=cons.col_default,
stat=cons.stat_default,
show_stations=cons.show_stations_default,
)
bokeh_map_plot_params = {"bokeh_map_data_dict":bokeh_map_data_dict,"show_stations":cons.show_stations_default}
map_plot = timeit(func=bokeh_map_plot, params=bokeh_map_plot_params)
logging.info("Initialise map plot end")

# create call back function for bokeh dashboard interaction
def callback_map_plot(attr, old, new):
logging.info("Callback map plot begin")
# extract new selector value
col = map_col_selector.value
stat = map_stat_selector.value
year = map_year_selector.value
show_stations = toggle_stations.active
# update bokeh data
bokeh_map_data_params = {"map_data":map_data,"station_data":station_data,"col":col,"stat":stat,"year":year}
bokeh_map_data_dict = timeit(func=bokeh_map_data, params=bokeh_map_data_params)
# update bokeh plot
map_plot = bokeh_map_plot(
bokeh_map_data_dict=bokeh_map_data_dict,
pointgeosource=pointgeosource,
col=col,
stat=stat,
show_stations=show_stations,
)
bokeh_map_plot_params = {"bokeh_map_data_dict":bokeh_map_data_dict,"show_stations":show_stations}
map_plot = timeit(func=bokeh_map_plot, params=bokeh_map_plot_params)
# reassign bokeh plot to bokeh dashboard
dashboard_map.children[1] = map_plot
logging.info("Callback map plot end")

# set up selectors for bokeh map plot
map_col_selector = Select(
Expand All @@ -68,6 +69,14 @@ def callback_map_plot(attr, old, new):
height=60,
aspect_ratio=10,
)
map_year_selector = Select(
title="Year:",
value=cons.linedash_year_timespan[1],
options=cons.linedash_year_options,
width=120,
height=60,
aspect_ratio=10,
)
toggle_stations = CheckboxButtonGroup(
labels=["Toggle Stations"],
active=[],
Expand All @@ -77,11 +86,10 @@ def callback_map_plot(attr, old, new):
map_col_selector.on_change("value", callback_map_plot)
map_stat_selector.on_change("value", callback_map_plot)
toggle_stations.on_change("active", callback_map_plot)
map_year_selector.on_change("value", callback_map_plot)

# structure dashboard map plot
widgets_map = column(
toggle_stations, map_col_selector, map_stat_selector
)
widgets_map = column(toggle_stations, map_col_selector, map_stat_selector, map_year_selector)
dashboard_map = row(widgets_map, map_plot)

return dashboard_map
58 changes: 37 additions & 21 deletions dashboard/utilities/bokeh_map_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,58 @@

@beartype
def bokeh_map_data(
map_data_dict:dict,
station_data:gpd.GeoDataFrame
map_data:gpd.GeoDataFrame,
station_data:gpd.GeoDataFrame,
col:str,
stat:str,
year:str
):
"""Generates the data used in the bokeh map plot.
Parameters
----------
map_data_dict : dict
map_data : gpd.GeoDataFrame
The aggregated geopandas data to be transformed into aggregated bokeh data objects for visualisation
station_data : geopandas.DataFrame
The geospatial Met Eireann station data to be transformed into a bokeh data object for visualisation
col : str
The climate measure category to plot in the interactive bokeh heatmap
stat : str
The aggregated statistic to plot in the interactive bokeh heatmap
year : str
The year of the data to filter for
Returns
-------
dict, bokeh.models.GeoJSONDataSource
dict
The aggregated and station bokeh data objects to visualise
"""
bokeh_map_data_dict = {}
for stat, map_data in map_data_dict.items():
tmp_data_dict = {}
# split data into missing and nonmissing
nonmiss_map_data = map_data[map_data[cons.col_options].notnull().any(axis=1)]
miss_map_data = map_data[~map_data[cons.col_options].notnull().any(axis=1)]
# Input GeoJSON source that contains features for plotting
missgeosource = GeoJSONDataSource(geojson=miss_map_data.to_json())
nonmissgeosource = GeoJSONDataSource(geojson=nonmiss_map_data.to_json())
# assign data to temp dict
tmp_data_dict["map_data"] = map_data
tmp_data_dict["nonmiss_map_data"] = nonmiss_map_data
tmp_data_dict["miss_map_data"] = miss_map_data
tmp_data_dict["missgeosource"] = missgeosource
tmp_data_dict["nonmissgeosource"] = nonmissgeosource
# assign temp data dict to bokeh data dict
bokeh_map_data_dict[stat] = tmp_data_dict
# filter for year
row_filter = (map_data['year'].astype(str) == year) & (map_data['stat'].astype(str) == stat)
sub_cols = ['county', 'geometry', 'year', 'stat', col]
map_data_sub = map_data.loc[row_filter, sub_cols]
# split data into missing and nonmissing
nonmiss_map_data = map_data_sub[map_data_sub.notnull().all(axis=1)]
miss_map_data = map_data_sub[map_data_sub.isnull().any(axis=1)]
# Input GeoJSON source that contains features for plotting
missgeosource = GeoJSONDataSource(geojson=miss_map_data.to_json())
nonmissgeosource = GeoJSONDataSource(geojson=nonmiss_map_data.to_json())
pointgeosource = GeoJSONDataSource(geojson=station_data.to_json())
# calculate colour bar range
color_mapper_low = nonmiss_map_data[col].min()
color_mapper_high = nonmiss_map_data[col].max()
# assign data to temp dict
bokeh_map_data_dict["col"] = col
bokeh_map_data_dict["map_data"] = map_data
bokeh_map_data_dict["nonmiss_map_data"] = nonmiss_map_data
bokeh_map_data_dict["miss_map_data"] = miss_map_data
bokeh_map_data_dict["missgeosource"] = missgeosource
bokeh_map_data_dict["nonmissgeosource"] = nonmissgeosource
bokeh_map_data_dict["pointgeosource"] = pointgeosource
bokeh_map_data_dict["color_mapper_low"] = color_mapper_low
bokeh_map_data_dict["color_mapper_high"] = color_mapper_high
# pickle the bokeh map data dictionary to disk
# with open(cons.bokeh_map_data_fpath, 'wb') as f:
# pickle.dump(bokeh_map_data_dict, f, protocol = pickle.HIGHEST_PROTOCOL)
return bokeh_map_data_dict, pointgeosource
return bokeh_map_data_dict
37 changes: 12 additions & 25 deletions dashboard/utilities/bokeh_map_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
@beartype
def bokeh_map_plot(
bokeh_map_data_dict:dict,
pointgeosource:GeoJSONDataSource,
col:str,
stat:str,
show_stations:list
) -> figure:
"""Generates the data used in the bokeh map plot.
Expand All @@ -19,12 +16,6 @@ def bokeh_map_plot(
----------
bokeh_map_data_dict : dict
A dictionary of bokeh aggregated data objects to construct the interactive bokeh heatmap with
pointgeosource : geopandas.DataFrame
The geospatial Met Eireann station data to overlay was red dots in the interactive bokeh heatmap
col : str
The climate measure category to plot in the interactive bokeh heatmap
stat : str
The aggregated statistic to plot in the interactive bokeh heatmap
show_stations : list
Whether to toggle the Met Eireann station data overlay in the interactive bokeh heatmap
Expand All @@ -37,13 +28,11 @@ def bokeh_map_plot(
lightblue = Color("lightblue")
steelblue = Color("steelblue")
palette = tuple([col.get_hex() for col in lightblue.range_to(steelblue, 100)])
# palette = ("lightblue", "steelblue")
# instantiate LinearColorMapper that linearly maps numbers in a range, into a sequence of colors.
nonmiss_map_data = bokeh_map_data_dict[stat]["nonmiss_map_data"]
# instantiate LinearColorMapper that linearly maps numbers in a range, into a sequence of colours.
color_mapper = LinearColorMapper(
palette=palette,
low=nonmiss_map_data[col].min(),
high=nonmiss_map_data[col].max(),
low=bokeh_map_data_dict["color_mapper_low"],
high=bokeh_map_data_dict["color_mapper_high"],
)
# create color bar.
color_bar = ColorBar(
Expand All @@ -57,9 +46,7 @@ def bokeh_map_plot(
major_label_text_font_size="18px",
)
# create underlying figure object
map_plot = figure(
toolbar_location="below", output_backend="webgl", **cons.FIG_SETTING
)
map_plot = figure(toolbar_location="below", output_backend="webgl", **cons.FIG_SETTING)
map_plot.axis.visible = False
map_plot.xgrid.grid_line_color = None
map_plot.ygrid.grid_line_color = None
Expand All @@ -70,9 +57,9 @@ def bokeh_map_plot(
map_plot.title.text_font_style = "bold"
map_plot.title.text_font_size = "22px"
# add patches to render states with no aggregate data
missgeosource = bokeh_map_data_dict[stat]["missgeosource"]
missgeosource = bokeh_map_data_dict['missgeosource']#[year]['miss_map_dataview']
misscounties = map_plot.patches(
"xs", "ys", source=missgeosource, fill_color="white", **cons.MAP_SETTINGS
xs="xs", ys="ys", source=missgeosource, fill_color="slategray", **cons.MAP_SETTINGS
)
map_plot.add_tools(
HoverTool(
Expand All @@ -83,26 +70,26 @@ def bokeh_map_plot(
)
)
# add patches to render states with aggregate data
nonmissgeosource = bokeh_map_data_dict[stat]["nonmissgeosource"]
nonmissgeosource = bokeh_map_data_dict['nonmissgeosource']
nonmisscounties = map_plot.patches(
"xs",
"ys",
xs="xs",
ys="ys",
source=nonmissgeosource,
fill_color={"field": col, "transform": color_mapper},
fill_color={"field": bokeh_map_data_dict['col'], "transform": color_mapper},
**cons.MAP_SETTINGS,
)
map_plot.add_tools(
HoverTool(
renderers=[nonmisscounties],
tooltips=[("County Name", "@county"), ("County Value", f"@{col}")],
tooltips=[("County Name", "@county"), ("County Value", f"@{bokeh_map_data_dict['col']}")],
attachment="left",
mode="mouse",
)
)
# add points to render stations
if show_stations == [0]:
stationpoints = map_plot.scatter(
"x", "y", source=pointgeosource, color="red", size=8, alpha=0.3
x="x", y="y", source=bokeh_map_data_dict['pointgeosource'], color="red", size=8, alpha=0.3
)
map_plot.add_tools(
HoverTool(
Expand Down
6 changes: 5 additions & 1 deletion dashboard/arch/timeit.py → dashboard/utilities/timeit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time
import numpy as np
import logging


def timeit(func, params, itr=1, digits=3):
Expand Down Expand Up @@ -27,5 +28,8 @@ def timeit(func, params, itr=1, digits=3):
t1 = time.time()
tres = t1 - t0
eres = round(np.mean(tres), digits)
print(f"Mean Execution Time: {eres} seconds")
logging_message=f"Execution Time for {str(func)}: {eres} seconds"
if itr>1:
logging_message=f"Mean {logging_message}"
logging.info(logging_message)
return res
Binary file modified data/gis/map_data.pickle
Binary file not shown.
Loading

0 comments on commit 8f0cda9

Please sign in to comment.