Source code for shyft.dashboard.maps.map_viewer

from typing import Dict, List, Tuple

from bokeh import __version__ as bokeh_version
import bokeh.models
from bokeh.plotting import figure as bokeh_figure

from shyft.dashboard.base.ports import Receiver

from shyft.dashboard.maps.map_layer import MapLayer
from shyft.dashboard.maps.base_map import BaseMap
from shyft.dashboard.maps.map_axes import MapAxesRanges
from shyft.dashboard.maps.layer_data import LayerDataHandle


[docs] class MapViewer: """ MapViewer Backend for plots of Maps with custom layers Examples -------- | from statkraft.bokeh.maps.map_viewer import MapViewer | from statkraft.bokeh.maps.layer_data import LayerDataHandle | from statkraft.bokeh.maps.map_layer import MapLayer | from statkraft.bokeh.test.maps.test_map_fixtures import basemap_factory | | # initialisation | base_map = basemap_factory() | # create map viewer | map_viewer = MapViewer(width=300, height=300, base_map=base_map, extent_padding=10) | # add layer | layer1 = MapLayer(map_viewer=map_viewer, name='first layer', layer_type=LayerType.POINT, | glyph_fixed_kwargs={'radius': 5}, | glyph_variable_kwargs={'x': 'x', 'y': 'y'}, | ) | # update the data with layer_data_handle | min_x = 26621 + 22373*0.5 + 50 | min_y = 6599653 + 18731*0.5 + 50 | max_x = 26621 + 22373*0.5 + 100 | max_y = 6599653 + 18731*0.5 + 100 | | updated_data = {'x': [min_x, max_x], | 'y': [min_y, max_y]} | layer_data_handle = LayerDataHandle(map_layer=layer1, data=updated_data) | | map_viewer.receive_layer_data_handles([layer_data_handle]) """
[docs] def __init__(self, *, base_map: BaseMap, width: int = 300, height: int = 300, extent_padding: float = 0) -> None: """ MapViewer Backend for plots of Maps with custom layers Parameters ---------- base_map: BaseMap instance to use in the back ground width: with of the figure height: height of the figure extent_padding: extra padding for the axis, when snapping to all objects in layers """ self.base_map = base_map self.fig_axes_ranges = MapAxesRanges(width=width, height=height, padding=extent_padding) self.zoom = bokeh.models.WheelZoomTool() self.pan = bokeh.models.PanTool() self.layers: Dict['statkraft.bokeh.composables.aqua_composables.Layer': bokeh.models.Renderer] = {} self.base_tools = [self.zoom, self.pan] self.hover_tools = [] self.legend = bokeh.models.Legend() if bokeh_version < '3.0.0': self.bokeh_figure = bokeh_figure(tools=self.base_tools, lod_threshold=None, plot_width=width, plot_height=height, background_fill_color='white', active_scroll=self.zoom, active_drag=self.pan, match_aspect=True # TODO: Map needs equal axes. This arg implemented in bokeh 0.12.7. For now, map must be square. ) else: self.bokeh_figure = bokeh_figure(tools=self.base_tools, lod_threshold=None, width=width, height=height, background_fill_color='white', active_scroll=self.zoom, active_drag=self.pan, match_aspect=True # TODO: Map needs equal axes. This arg implemented in bokeh 0.12.7. For now, map must be square. ) self.bokeh_figure.axis.visible = False self.fig_axes_ranges.bind(parent=self) self.receive_layer_data_handles = Receiver(parent=self, name="receive layer data to update map layer", signal_type=List[LayerDataHandle], func=self._receive_layer_data_handles) if self.base_map: self.bokeh_figure.add_tile(self.base_map.tiles) self.layout = self.bokeh_figure
[docs] def add_layer(self, map_layer: MapLayer) -> Tuple[bokeh.models.Renderer, bokeh.models.HoverTool]: """ Function to add a new layer to the Map This is called from the Layer constructor """ renderer = self.bokeh_figure.add_glyph(map_layer.source, map_layer.glyph) hover_tool = None if map_layer.hover_tool_tips is not None: hover_tool = bokeh.models.HoverTool(renderers=[renderer], tooltips=map_layer.hover_tool_tips) self.bokeh_figure.add_tools(hover_tool) # register renderer self.layers[map_layer] = renderer return renderer, hover_tool
# def _add_legend_items(self, *, text: str, glyphs: list) -> None: # self.legend.items = [item for item in self.legend.items if # not item.label == text] # Remove previous items with matching labels. # self.legend.items.append(bokeh.models.LegendItem(label=text, renderers=glyphs))
[docs] def update_axes(self) -> None: """ This function updates the figure axis based on layer bbox """ bounds_list = [] for layer in self.layers.keys(): if not layer.update_axes: continue bbox = layer.bbox if bbox is None: continue bounds_list.append(bbox) if bounds_list: self.fig_axes_ranges.set_axes_bounds_from_bounds_list(bounds_list)
def _receive_layer_data_handles(self, layer_data_list: List[LayerDataHandle]) -> None: """ receiver function to receive new data handles """ # update data of layers for layer_data_handle in layer_data_list: if layer_data_handle.map_layer not in self.layers.keys(): continue layer_data_handle.map_layer.updated_data(new_data=layer_data_handle.data) # update axes self.update_axes()