def test_state_equality_valueerror_lengths_must_match(): """should return False if lengths do not match""" valid_times = ( pd.date_range("2020-01-01", periods=2), pd.date_range("2020-01-01", periods=3), ) left = db.State(valid_times=valid_times[0]) right = db.State(valid_times=valid_times[1]) assert (left == right) == False
def test_support_old_style_state(self): """Not all components are ready to accept dict() states""" listener = unittest.mock.Mock() store = redux.Store(db.reducer) old_states = ( rx.Stream().listen_to(store).map(lambda x: db.State(**x))) old_states.subscribe(listener) store.dispatch(db.set_value("pressure", 1000)) expect = db.State(pressure=1000) listener.assert_called_once_with(expect)
def test_image_given_empty_state(self): name = None pattern = None locator = None state = db.State() loader = data.DBLoader(name, pattern, locator) result = loader.image(state) expect = self.empty_image self.assert_dict_equal(expect, result)
def test_image_given_empty_state(self): name = None pattern = None locator = None state = db.State() loader = forest.drivers.unified_model.Loader(name, pattern, locator) result = loader.image(state) expect = self.empty_image self.assert_dict_equal(expect, result)
def test_image_given_non_existent_entry_in_database(self): name = None pattern = None database = db.Database.connect(":memory:") locator = db.Locator(database.connection) state = db.State(variable="variable", initial_time="2019-01-01 00:00:00", valid_time="2019-01-01 00:00:00", pressure=1000.) loader = data.DBLoader(name, pattern, locator) result = loader.image(state) expect = self.empty_image self.assert_dict_equal(expect, result)
def test_image_given_inconsistent_pressures(self): path = "file.nc" variable = "variable" initial_time = "2019-01-01 00:00:00" valid_time = "2019-01-01 00:00:00" pressure = 1000. database = db.Database.connect(":memory:") database.insert_file_name(path, initial_time) database.insert_pressure(path, variable, pressure, i=0) database.insert_time(path, variable, valid_time, i=0) locator = db.Locator(database.connection) state = db.State(variable=variable, initial_time=initial_time, valid_time=valid_time, pressure=pressure, pressures=[925.]) loader = data.DBLoader(None, "*.nc", locator) result = loader.image(state) expect = self.empty_image self.assert_dict_equal(expect, result)
def main(argv=None): args = parse_args.parse_args(argv) if len(args.files) > 0: config = cfg.from_files(args.files, args.file_type) else: config = cfg.load_config(args.config_file) database = None if args.database is not None: if args.database != ':memory:': assert os.path.exists(args.database), "{} must exist".format( args.database) database = db.Database.connect(args.database) # Full screen map lon_range = (90, 140) lat_range = (-23.5, 23.5) x_range, y_range = geo.web_mercator(lon_range, lat_range) figure = bokeh.plotting.figure(x_range=x_range, y_range=y_range, x_axis_type="mercator", y_axis_type="mercator", active_scroll="wheel_zoom") tile = bokeh.models.WMTSTileSource( url="https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}.png", attribution="") figures = [figure] for _ in range(2): f = bokeh.plotting.figure(x_range=figure.x_range, y_range=figure.y_range, x_axis_type="mercator", y_axis_type="mercator", active_scroll="wheel_zoom") figures.append(f) for f in figures: f.axis.visible = False f.toolbar.logo = None f.toolbar_location = None f.min_border = 0 f.add_tile(tile) figure_row = bokeh.layouts.row(*figures, sizing_mode="stretch_both") figure_row.children = [figures[0]] # Trick to keep correct sizing modes figure_drop = bokeh.models.Dropdown(label="Figure", menu=[(str(i), str(i)) for i in [1, 2, 3]]) def on_change(attr, old, new): if int(new) == 1: figure_row.children = [figures[0]] elif int(new) == 2: figure_row.children = [figures[0], figures[1]] elif int(new) == 3: figure_row.children = [figures[0], figures[1], figures[2]] figure_drop.on_change("value", on_change) color_mapper = bokeh.models.LinearColorMapper( low=0, high=1, palette=bokeh.palettes.Plasma[256]) for figure in figures: colorbar = bokeh.models.ColorBar(color_mapper=color_mapper, orientation="horizontal", background_fill_alpha=0., location="bottom_center", major_tick_line_color="black", bar_line_color="black") figure.add_layout(colorbar, 'center') # Database/File system loader(s) for group in config.file_groups: if group.label not in data.LOADERS: if group.locator == "database": loader = load.Loader.group_args(group, args, database=database) else: loader = load.Loader.group_args(group, args) data.add_loader(group.label, loader) renderers = {} viewers = {} for name, loader in data.LOADERS.items(): if isinstance(loader, rdt.Loader): viewer = rdt.View(loader) elif isinstance(loader, earth_networks.Loader): viewer = earth_networks.View(loader) elif isinstance(loader, data.GPM): viewer = view.GPMView(loader, color_mapper) elif isinstance(loader, satellite.EIDA50): viewer = view.EIDA50(loader, color_mapper) else: viewer = view.UMView(loader, color_mapper) viewers[name] = viewer renderers[name] = [viewer.add_figure(f) for f in figures] artist = Artist(viewers, renderers) renderers = [] for _, r in artist.renderers.items(): renderers += r image_sources = [] for name, viewer in artist.viewers.items(): if isinstance(viewer, (view.UMView, view.GPMView, view.EIDA50)): image_sources.append(viewer.source) # Lakes for figure in figures: add_feature(figure, data.LAKES, color="lightblue") features = [] for figure in figures: features += [ add_feature(figure, data.COASTLINES), add_feature(figure, data.BORDERS) ] # Disputed borders for figure in figures: add_feature(figure, data.DISPUTED, color="red") toggle = bokeh.models.CheckboxButtonGroup(labels=["Coastlines"], active=[0], width=135) def on_change(attr, old, new): if len(new) == 1: for feature in features: feature.visible = True else: for feature in features: feature.visible = False toggle.on_change("active", on_change) dropdown = bokeh.models.Dropdown(label="Color", menu=[("Black", "black"), ("White", "white")], width=50) autolabel(dropdown) def on_change(attr, old, new): for feature in features: feature.glyph.line_color = new dropdown.on_change("value", on_change) slider = bokeh.models.Slider(start=0, end=1, step=0.1, value=1.0, show_value=False) def is_image(renderer): return isinstance(getattr(renderer, 'glyph', None), bokeh.models.Image) image_renderers = [r for r in renderers if is_image(r)] custom_js = bokeh.models.CustomJS(args=dict(renderers=image_renderers), code=""" renderers.forEach(function (r) { r.glyph.global_alpha = cb_obj.value }) """) slider.js_on_change("value", custom_js) colors_controls = colors.Controls(color_mapper, "Plasma", 256) mapper_limits = MapperLimits(image_sources, color_mapper) menu = [] for k, _ in config.patterns: menu.append((k, k)) image_controls = images.Controls(menu) def on_change(attr, old, new): if int(new) == 1: image_controls.labels = ["Show"] elif int(new) == 2: image_controls.labels = ["L", "R"] elif int(new) == 3: image_controls.labels = ["L", "C", "R"] figure_drop.on_change("value", on_change) image_controls.subscribe(artist.on_visible) div = bokeh.models.Div(text="", width=10) border_row = bokeh.layouts.row(bokeh.layouts.column(toggle), bokeh.layouts.column(div), bokeh.layouts.column(dropdown)) # Pre-select first layer for name, _ in config.patterns: image_controls.select(name) break navigator = navigate.Navigator(config, database) # Pre-select menu choices (if any) initial_state = {} for _, pattern in config.patterns: initial_state = db.initial_state(navigator, pattern=pattern) break middlewares = [ db.Log(verbose=True), keys.navigate, db.InverseCoordinate("pressure"), db.next_previous, db.Controls(navigator), db.Converter({ "valid_times": db.stamps, "inital_times": db.stamps }) ] store = redux.Store(db.reducer, initial_state=initial_state, middlewares=middlewares) controls = db.ControlView() controls.subscribe(store.dispatch) store.subscribe(controls.render) old_states = (db.Stream().listen_to(store).map(lambda x: db.State(**x))) old_states.subscribe(artist.on_state) # Ensure all listeners are pointing to the current state store.notify(store.state) store.dispatch(db.set_value("patterns", config.patterns)) tabs = bokeh.models.Tabs(tabs=[ bokeh.models.Panel(child=bokeh.layouts.column( bokeh.models.Div(text="Navigate:"), controls.layout, bokeh.models.Div(text="Compare:"), bokeh.layouts.row(figure_drop), image_controls.column), title="Control"), bokeh.models.Panel(child=bokeh.layouts.column( border_row, bokeh.layouts.row(slider), colors_controls.layout, bokeh.layouts.row(mapper_limits.low_input), bokeh.layouts.row(mapper_limits.high_input), bokeh.layouts.row(mapper_limits.checkbox), ), title="Settings") ]) # Series sub-figure widget series_figure = bokeh.plotting.figure(plot_width=400, plot_height=200, x_axis_type="datetime", toolbar_location=None, border_fill_alpha=0) series_figure.toolbar.logo = None series_row = bokeh.layouts.row(series_figure, name="series") def place_marker(figure, source): figure.circle(x="x", y="y", color="red", source=source) def cb(event): source.data = {"x": [event.x], "y": [event.y]} return cb marker_source = bokeh.models.ColumnDataSource({"x": [], "y": []}) series = Series.from_groups(series_figure, config.file_groups, directory=args.directory) old_states.subscribe(series.on_state) for f in figures: f.on_event(bokeh.events.Tap, series.on_tap) f.on_event(bokeh.events.Tap, place_marker(f, marker_source)) # Minimise controls to ease navigation compact_button = bokeh.models.Button(label="Compact") compact_minus = bokeh.models.Button(label="-", width=50) compact_plus = bokeh.models.Button(label="+", width=50) compact_navigation = bokeh.layouts.column( compact_button, bokeh.layouts.row(compact_minus, compact_plus, width=100)) control_root = bokeh.layouts.column(compact_button, tabs, name="controls") display = "large" def on_compact(): nonlocal display if display == "large": control_root.height = 100 control_root.width = 120 compact_button.width = 100 compact_button.label = "Expand" control_root.children = [compact_navigation] display = "compact" else: control_root.height = 500 control_root.width = 300 compact_button.width = 300 compact_button.label = "Compact" control_root.children = [compact_button, tabs] display = "large" compact_button.on_click(on_compact) # Add key press support key_press = keys.KeyPress() key_press.subscribe(store.dispatch) document = bokeh.plotting.curdoc() document.title = "FOREST" document.add_root(control_root) document.add_root(series_row) document.add_root(figure_row) document.add_root(key_press.hidden_button)
import pytest import datetime as dt import numpy as np import pandas as pd import cftime import bokeh.palettes import forest.state from forest import db from forest.db.control import time_array_equal @pytest.mark.parametrize( "left,right,expect", [(db.State(), db.State(), True), (db.State(valid_time="2019-01-01 00:00:00"), db.State(valid_time=dt.datetime(2019, 1, 1)), True), (db.State(initial_time="2019-01-01 00:00:00"), db.State(initial_time=dt.datetime(2019, 1, 1)), True), (db.State(initial_times=np.array(["2019-01-01 00:00:00"], dtype='datetime64[s]')), db.State(initial_times=["2019-01-01 00:00:00"]), True), (db.State(initial_times=[]), db.State(initial_times=["2019-01-01 00:00:00"]), False), (db.State( valid_times=np.array(["2019-01-01 00:00:00"], dtype='datetime64[s]')), db.State(valid_times=["2019-01-01 00:00:00"]), True), (db.State(pressure=1000.001), db.State(pressure=1000.0001), True), (db.State(pressure=1000.001), db.State(pressure=900), False), (db.State(pressures=np.array([1000.001, 900])), db.State(pressures=[1000.0001, 900]), True), (db.State(pressures=[1000.001, 900]), db.State(pressures=[900, 900]),
def _to_old(state): kwargs = {k: state.get(k, None) for k in db.State._fields} return db.State(**kwargs)