def load_data(path, itime, ipressure): step = 100 with netCDF4.Dataset(path) as dataset: var = dataset.variables["x_wind"] if len(var.dimensions) == 4: values = var[itime, ipressure] else: values = var[itime] u = values[::step, ::step] var = dataset.variables["y_wind"] if len(var.dimensions) == 4: values = var[itime, ipressure] else: values = var[itime] v = values[::step, ::step] for d in var.dimensions: if "longitude" in d: lons = dataset.variables[d][::step] if "latitude" in d: lats = dataset.variables[d][::step] gx, _ = geo.web_mercator(lons, np.zeros(len(lons), dtype="d")) _, gy = geo.web_mercator(np.zeros(len(lats), dtype="d"), lats) x, y = np.meshgrid(gx, gy) u = convert_units(u, 'm s-1', 'knots') v = convert_units(v, 'm s-1', 'knots') return { "x": x.flatten(), "y": y.flatten(), "u": u.flatten(), "v": v.flatten() }
def load_centre_points(path): """Holds a centre point, future point and future movement line""" with open(path) as stream: rdt = json.load(stream) # Create an empty dictionary mydict = dict( x1=[], y1=[], x2=[], y2=[], xs=[], ys=[], Arrowxs=[], Arrowys=[], LonG=[], LatG=[], NumIdCell=[], NumIdBirth=[], MvtSpeed=[], MvtDirection=[]) # Loop through features for i, feature in enumerate(rdt["features"]): # Append data from the feature properties to the dictionary mykeys = [k for k in mydict.keys() if not (('x' in k) or ('y' in k))] for k in mykeys: try: thisdata, units = descale_rdt(k, feature['properties'][k]) mydict[k].append(thisdata) except: mydict[k].append(None) # Takes the trajectory lat/lons, reprojects and puts them in a list within a list lon = feature['properties']['LonG'] lat = feature['properties']['LatG'] x1, y1 = geo.web_mercator(lon, lat) mydict['x1'].extend(x1) mydict['y1'].extend(y1) # Now calculate future point and line try: speed = float(feature['properties']['MvtSpeed']) except ValueError: speed = 0 try: direction = float(feature['properties']['MvtDirection']) except ValueError: direction = 0 lon2, lat2 = calc_dst_point(lon, lat, speed, direction) x2, y2 = geo.web_mercator(lon2, lat2) mydict['x2'].extend(x2) mydict['y2'].extend(y2) mydict['xs'].append([x1, x2]) mydict['ys'].append([y1, y2]) # Now calculate arrow polygon x3d, y3d, x4d, y4d = get_arrow_poly(lon2, lat2, speed, direction) [x3, x4], [y3, y4] = geo.web_mercator([x3d, x4d], [y3d, y4d]) mydict['Arrowxs'].append([x2[0], x3, x4]) mydict['Arrowys'].append([y2[0], y3, y4]) return mydict
def load_tail_lines_json(path): """Load tail line data from file :returns: dict representation suitable for ColumnDataSource """ with open(path) as stream: rdt = json.load(stream) # Create an empty dictionary mydict = get_empty_feature_dict("Tail_Lines") # Loop through features for i, feature in enumerate(rdt["features"]): # Append data from the feature properties to the dictionary for k in mydict.keys(): try: thisdata, units = descale_rdt(k, feature["properties"][k]) mydict[k].append(thisdata) except: # Do nothing at the moment with the xs and ys if not k in ["xs", "ys"]: mydict[k].append(None) else: continue # Takes the trajectory lat/lons, reprojects and # puts them in a list within a list lons = feature["properties"]["LonTrajCellCG"] lats = feature["properties"]["LatTrajCellCG"] xs, ys = geo.web_mercator(lons, lats) mydict["xs"].append(xs) mydict["ys"].append(ys) return mydict
def xs_ys(lines): xs, ys = [], [] for lons, lats in lines: x, y = geo.web_mercator(lons, lats) xs.append(x) ys.append(y) return {"xs": xs, "ys": ys}
def xs_ys(lines): """Map to Web Mercator projection and bokeh multi_line structure""" xs, ys = [], [] for lons, lats in lines: x, y = geo.web_mercator(lons, lats) xs.append(x) ys.append(y) return {"xs": xs, "ys": ys}
def make_arrow(mydict, lon, lat, speed, direction): lon2, lat2 = calc_dst_point(lon, lat, speed, direction) x1, y1 = geo.web_mercator(lon, lat) x2, y2 = geo.web_mercator(lon2, lat2) mydict["x2"].extend(x2) mydict["y2"].extend(y2) mydict["xs"].append([x1, x2]) mydict["ys"].append([y1, y2]) # Now calculate arrow polygon x3d, y3d, x4d, y4d = get_arrow_poly(lon2, lat2, speed, direction) [x3, x4], [y3, y4] = geo.web_mercator([x3d, x4d], [y3d, y4d]) mydict["Arrowxs"].append([x2[0], x3, x4]) mydict["Arrowys"].append([y2[0], y3, y4]) return mydict
def render(self, valid_date): frame = self.loader.load_date(valid_date) x, y = geo.web_mercator(frame.longitude, frame.latitude) self.source.data = { "x": x, "y": y, "date": frame.date, "longitude": frame.longitude, "latitude": frame.latitude, "flash_type": frame.flash_type, }
def web_mercator(lons, lats): """Similar to forest.geo.web_mercator but preserves array shape""" if isinstance(lons, list): lons = np.asarray(lons) if isinstance(lats, list): lats = np.asarray(lats) if (lons.ndim == 1): gx, _ = geo.web_mercator( lons, np.zeros(len(lons), dtype="d")) _, gy = geo.web_mercator( np.zeros(len(lats), dtype="d"), lats) return gx, gy elif (lons.ndim == 2) and (lats.ndim == 2): gx, gy = geo.web_mercator(lons, lats) gx = gx.reshape(lons.shape) gx = np.ma.masked_invalid(gx) gy = gy.reshape(lats.shape) gy = np.ma.masked_invalid(gy) return gx, gy else: raise Exception("Either 1D or 2D lons/lats")
def load_polygon_json(path): """Load GeoJSON string representation of Polygons from file :returns: GeoJSON str """ with open(path) as stream: rdt = json.load(stream) # Convert units from the netcdf / geojson data file units into something more readable (e.g. could be Kelvin to degrees C or Pa to hPa) unitsToRescale = {"Pa": {"scale": 100, "offset": 0, "Units": "hPa"}} # Get text labels instead of numbers for certain fields fieldsToLookup = [ "PhaseLife", "SeverityType", "SeverityIntensity", "ConvType", "CType", ] copy = dict(rdt) for i, feature in enumerate(rdt["features"]): coordinates = feature["geometry"]["coordinates"][0] lons, lats = np.asarray(coordinates).T x, y = geo.web_mercator(lons, lats) c = np.array([x, y]).T.tolist() copy["features"][i]["geometry"]["coordinates"][0] = c for k in feature["properties"].keys(): # Might be easier to have a units lookup instead of this ... deldata, myunits = descale_rdt(k, feature["properties"][k]) if myunits in unitsToRescale.keys(): try: mydict = unitsToRescale.get(myunits) scale, offset, units = mydict.values() conv_data = (feature["properties"][k] / scale) + offset copy["features"][i]["properties"][k] = conv_data except: continue if k in fieldsToLookup: try: copy["features"][i]["properties"][k] = fieldValueLUT( k, feature["properties"][k]) except: continue return json.dumps(copy)
def load_tail_points(path): with open(path) as stream: rdt = json.load(stream) # Create an empty dictionary mydict = dict( x=[], y=[], LonTrajCellCG=[], LatTrajCellCG=[], NumIdCell=[], NumIdBirth=[], DTimeTraj=[], BTempTraj=[], BTminTraj=[], BaseAreaTraj=[], TopAreaTraj=[], CoolingRateTraj=[], ExpanRateTraj=[], SpeedTraj=[], DirTraj=[]) # Loop through features for i, feature in enumerate(rdt["features"]): # Append data from the feature properties to the dictionary # First though, how many points do we have in the trajectory tail? npts = len(feature['properties']['LonTrajCellCG']) mykeys = [k for k in mydict.keys() if k not in ['x', 'y']] for k in mykeys: # print(k, type(feature['properties'][k]), sep=':') try: if not isinstance(feature['properties'][k], list): datalist = [i for i in itertools.repeat(feature['properties'][k],npts)] thisdata, units = descale_rdt(k, datalist) mydict[k].extend(datalist) else: thisdata, units = descale_rdt(k, feature['properties'][k]) mydict[k].extend(thisdata) except: datalist = [i for i in itertools.repeat(None, npts)] mydict[k].extend(datalist) # Takes the trajectory lat/lons, reprojects and puts them in a list within a list lons = feature['properties']['LonTrajCellCG'] lats = feature['properties']['LatTrajCellCG'] x, y = geo.web_mercator(lons, lats) mydict['x'].extend(x) mydict['y'].extend(y) return mydict
def load_tail_lines(path): """Load tail line data from file :returns: dict representation suitable for ColumnDataSource """ with open(path) as stream: rdt = json.load(stream) # Create an empty dictionary mydict = dict( xs=[], ys=[], LonTrajCellCG=[], LatTrajCellCG=[], NumIdCell=[], NumIdBirth=[], DTimeTraj=[], BTempTraj=[], BTminTraj=[], BaseAreaTraj=[], TopAreaTraj=[], CoolingRateTraj=[], ExpanRateTraj=[], SpeedTraj=[], DirTraj=[]) # Loop through features for i, feature in enumerate(rdt["features"]): # Append data from the feature properties to the dictionary for k in mydict.keys(): try: thisdata, units = descale_rdt(k, feature['properties'][k]) mydict[k].append(thisdata) except: # Do nothing at the moment with the xs and ys if not k in ['xs', 'ys']: mydict[k].append(None) else: continue # Takes the trajectory lat/lons, reprojects and # puts them in a list within a list lons = feature['properties']['LonTrajCellCG'] lats = feature['properties']['LatTrajCellCG'] xs, ys = geo.web_mercator(lons, lats) mydict['xs'].append(xs) mydict['ys'].append(ys) return mydict
def load_centre_points_json(path): """Holds a centre point, future point and future movement line""" with open(path) as stream: rdt = json.load(stream) # Create an empty dictionary mydict = get_empty_feature_dict("Centre_Point") # Loop through features for i, feature in enumerate(rdt["features"]): # Append data from the feature properties to the dictionary mykeys = [ k for k in mydict.keys() if not (("x" in k) or ("y" in k)) ] for k in mykeys: try: thisdata, units = descale_rdt(k, feature["properties"][k]) mydict[k].append(thisdata) except: mydict[k].append(None) # Takes the trajectory lat/lons, reprojects and puts them in a list within a list lon = feature["properties"]["LonG"] lat = feature["properties"]["LatG"] x1, y1 = geo.web_mercator(lon, lat) mydict["x1"].extend(x1) mydict["y1"].extend(y1) # Now calculate future point and line try: speed = float(feature["properties"]["MvtSpeed"]) except ValueError: speed = 0 try: direction = float(feature["properties"]["MvtDirection"]) except ValueError: direction = 0 mydict = make_arrow(mydict, lon, lat, speed, direction) return mydict
def figure(): lon_range = (0, 30) lat_range = (0, 30) 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") figure.axis.visible = False figure.toolbar.logo = None figure.toolbar_location = None figure.min_border = 3 tile = bokeh.models.WMTSTileSource( url="https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}.png", attribution="") figure.add_tile(tile) legend = bokeh.models.Legend() figure.add_layout(legend) return figure
def scatter(self, state): """Scatter plot of flash position colored by time since flash""" valid_time = _to_datetime(state.valid_time) paths = self.locator.find(valid_time) frame = self.loader.load(paths) frame = self.select_date(frame, valid_time) frame = frame[:400] # Limit points frame["time_since_flash"] = self.since_flash(frame["date"], valid_time) if len(frame) == 0: return self.empty_image x, y = geo.web_mercator(frame.longitude, frame.latitude) self.color_mapper.low = np.min(frame.time_since_flash) self.color_mapper.high = np.max(frame.time_since_flash) self.sources["scatter"].data = { "x": x, "y": y, "date": frame.date, "longitude": frame.longitude, "latitude": frame.latitude, "flash_type": frame.flash_type, "time_since_flash": frame.time_since_flash, }
def load_tail_points_json(path): with open(path) as stream: rdt = json.load(stream) # Create an empty dictionary mydict = get_empty_feature_dict("Tail_Points") # Loop through features for i, feature in enumerate(rdt["features"]): # Append data from the feature properties to the dictionary # First though, how many points do we have in the trajectory tail? npts = len(feature["properties"]["LonTrajCellCG"]) mykeys = [k for k in mydict.keys() if k not in ["x", "y"]] for k in mykeys: # print(k, type(feature['properties'][k]), sep=':') try: if not isinstance(feature["properties"][k], list): datalist = [ i for i in itertools.repeat( feature["properties"][k], npts) ] thisdata, units = descale_rdt(k, datalist) mydict[k].extend(datalist) else: thisdata, units = descale_rdt(k, feature["properties"][k]) mydict[k].extend(thisdata) except: datalist = [i for i in itertools.repeat(None, npts)] mydict[k].extend(datalist) # Takes the trajectory lat/lons, reprojects and puts them in a list within a list lons = feature["properties"]["LonTrajCellCG"] lats = feature["properties"]["LatTrajCellCG"] x, y = geo.web_mercator(lons, lats) mydict["x"].extend(x) mydict["y"].extend(y) return mydict
def main(argv=None): args = parse_args.parse_args(argv) data.AUTO_SHUTDOWN = args.auto_shutdown if len(args.files) > 0: if args.config_file is not None: raise Exception( '--config-file and [FILE [FILE ...]] not compatible') config = cfg.from_files(args.files, args.file_type) else: config = cfg.Config.load(args.config_file, variables=cfg.combine_variables( os.environ, args.variables)) # Feature toggles if "feature" in config.plugins: features = plugin.call(config.plugins["feature"].entry_point) else: features = config.features data.FEATURE_FLAGS = features # Full screen map viewport = config.default_viewport x_range, y_range = geo.web_mercator(viewport.lon_range, viewport.lat_range) figure = bokeh.plotting.figure( x_range=x_range, y_range=y_range, #output_backend = "svg", x_axis_type="mercator", y_axis_type="mercator", active_scroll="wheel_zoom") 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 figure_row = layers.FigureRow(figures) color_mapper = bokeh.models.LinearColorMapper( low=0, high=1, palette=bokeh.palettes.Plasma[256]) # Convert config to datasets datasets = {} datasets_by_pattern = {} label_to_pattern = {} for group in config.file_groups: settings = { "label": group.label, "pattern": group.pattern, "locator": group.locator, "database_path": group.database_path, "directory": group.directory } dataset = drivers.get_dataset(group.file_type, settings) datasets[group.label] = dataset datasets_by_pattern[group.pattern] = dataset label_to_pattern[group.label] = group.pattern # Lakes for figure in figures: add_feature(figure, data.LAKES, color="lightblue") features = [] for figure in figures: render2 = add_feature(figure, data.LAKES, color="lightblue") 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.CheckboxGroup(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(event): for feature in features: feature.glyph.line_color = new dropdown.on_click(on_change) layers_ui = layers.LayersUI() div = bokeh.models.Div(text="", width=10) border_row = bokeh.layouts.row(bokeh.layouts.column(toggle), bokeh.layouts.column(div), bokeh.layouts.column(dropdown)) # Add optional sub-navigators sub_navigators = { key: dataset.navigator() for key, dataset in datasets_by_pattern.items() if hasattr(dataset, "navigator") } navigator = navigate.Navigator(sub_navigators) # Pre-select menu choices (if any) initial_state = {} for pattern, _ in sub_navigators.items(): initial_state = db.initial_state(navigator, pattern=pattern) break middlewares = [ keys.navigate, db.InverseCoordinate("pressure"), db.next_previous, db.Controls(navigator), # TODO: Deprecate this middleware colors.palettes, colors.middleware(), presets.Middleware(presets.proxy_storage(config.presets_file)), presets.middleware, layers.middleware, navigator, mws.echo, ] store = redux.Store(forest.reducer, initial_state=initial_state, middlewares=middlewares) # Colorbar user interface colorbar_ui = forest.components.ColorbarUI() colorbar_ui.connect(store) # Add time user interface time_ui = forest.components.TimeUI() time_ui.connect(store) # Connect MapView orchestration to store opacity_slider = forest.layers.OpacitySlider() source_limits = colors.SourceLimits().connect(store) factory_class = forest.layers.factory(color_mapper, figures, source_limits, opacity_slider) gallery = forest.layers.Gallery.from_datasets(datasets, factory_class) gallery.connect(store) # Connect layers controls layers_ui.add_subscriber(store.dispatch) layers_ui.connect(store) # Connect tools controls display_names = { "time_series": "Display Time Series", "profile": "Display Profile", "barc": "BARC Toolkit" } available_features = { k: display_names[k] for k in display_names.keys() if data.FEATURE_FLAGS[k] } tools_panel = tools.ToolsPanel(available_features) tools_panel.connect(store) #barc_toolbar=bokeh.models.tools.Toolbar(tools=barc_tools,logo=None) if data.FEATURE_FLAGS["BARC"]: barc = BARC(figures) tools_panel.layout.children.extend(barc.ToolBar()) # Navbar components navbar = Navbar(show_diagram_button=len(available_features) > 0) navbar.connect(store) # Connect tap listener tap_listener = screen.TapListener() tap_listener.connect(store) # Connect figure controls/views figure_ui = layers.FigureUI() figure_ui.add_subscriber(store.dispatch) figure_row.connect(store) # Tiling picker if config.use_web_map_tiles: tile_picker = forest.components.TilePicker() for figure in figures: tile_picker.add_figure(figure) tile_picker.connect(store) # Connect color palette controls colors.ColorMapperView(color_mapper).connect(store) color_palette = colors.ColorPalette().connect(store) # Connect limit controllers to store user_limits = colors.UserLimits().connect(store) # Preset preset_ui = presets.PresetUI().connect(store) # Connect navigation controls controls = db.ControlView() controls.connect(store) # Add support for a modal dialogue if data.FEATURE_FLAGS["multiple_colorbars"]: view = forest.components.modal.Tabbed() else: view = forest.components.modal.Default() modal = forest.components.Modal(view=view) modal.connect(store) # Set default time series visibility store.dispatch(tools.on_toggle_tool("time_series", False)) # Set default profile visibility store.dispatch(tools.on_toggle_tool("profile", False)) # Set top-level navigation store.dispatch(db.set_value("patterns", config.patterns)) # Pre-select first map_view layer for label, dataset in datasets.items(): pattern = label_to_pattern[label] for variable in navigator.variables(pattern): spec = { "label": label, "dataset": label, "variable": variable, "active": [0] } store.dispatch(forest.layers.save_layer(0, spec)) break break # Set variable dimensions (needed by modal dialogue) for label, dataset in datasets.items(): pattern = label_to_pattern[label] values = navigator.variables(pattern) store.dispatch(dimension.set_variables(label, values)) # Select web map tiling if config.use_web_map_tiles: store.dispatch(tiles.set_tile(tiles.STAMEN_TERRAIN)) store.dispatch(tiles.set_label_visible(True)) # Organise controls/settings layouts = {} layouts["controls"] = [ bokeh.models.Div(text="Layout:"), figure_ui.layout, bokeh.models.Div(text="Navigate:"), controls.layout, bokeh.models.Div(text="Compare:"), layers_ui.layout ] layouts["settings"] = [ border_row, opacity_slider.layout, preset_ui.layout, color_palette.layout, user_limits.layout, bokeh.models.Div(text="Tiles:"), ] if config.use_web_map_tiles: layouts["settings"].append(tile_picker.layout) tabs = bokeh.models.Tabs(tabs=[ bokeh.models.Panel(child=bokeh.layouts.column(*layouts["controls"]), title="Control"), bokeh.models.Panel(child=bokeh.layouts.column(*layouts["settings"]), title="Settings") ]) tool_figures = {} if data.FEATURE_FLAGS["time_series"]: # 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_view = series.SeriesView.from_groups(series_figure, config.file_groups) series_view.add_subscriber(store.dispatch) series_args = (rx.Stream().listen_to(store).map( series.select_args).filter(lambda x: x is not None).distinct()) series_args.map(lambda a: series_view.render(*a)) series_args.map(print) # Note: map(print) creates None stream tool_figures["series_figure"] = series_figure if data.FEATURE_FLAGS["profile"]: # Profile sub-figure widget profile_figure = bokeh.plotting.figure(plot_width=300, plot_height=450, toolbar_location=None, border_fill_alpha=0) profile_figure.toolbar.logo = None profile_figure.y_range.flipped = True profile_view = profile.ProfileView.from_groups(profile_figure, config.file_groups) profile_view.add_subscriber(store.dispatch) profile_args = (rx.Stream().listen_to(store).map( profile.select_args).filter(lambda x: x is not None).distinct()) profile_args.map(lambda a: profile_view.render(*a)) profile_args.map(print) # Note: map(print) creates None stream tool_figures["profile_figure"] = profile_figure tool_layout = tools.ToolLayout(**tool_figures) tool_layout.connect(store) for f in figures: f.on_event(bokeh.events.Tap, tap_listener.update_xy) marker = screen.MarkDraw(f).connect(store) control_root = bokeh.layouts.column(tabs, name="controls") # Add key press support key_press = keys.KeyPress() key_press.add_subscriber(store.dispatch) document = bokeh.plotting.curdoc() document.title = "FOREST" document.add_root(control_root) document.add_root( bokeh.layouts.column(tools_panel.layout, tool_layout.layout, width=400, name="series")) document.add_root(bokeh.layouts.row(time_ui.layout, name="time")) for root in navbar.roots: document.add_root(root) document.add_root(colorbar_ui.layout) document.add_root(figure_row.layout) document.add_root(key_press.hidden_button) document.add_root(modal.layout)
def image(self, state): """Image colored by time since flash or flash density""" valid_time = _to_datetime(state.valid_time) # 15 minute/1 hour slice of data? window = dt.timedelta(minutes=60) # 1 hour window paths = self.locator.find_period(valid_time, window) frame = self.loader.load(paths) frame = self.select_date(frame, valid_time, window) # Filter intra-cloud/cloud-ground rows if "intra-cloud" in state.variable.lower(): frame = frame[frame["flash_type"] == "IC"] elif "cloud-ground" in state.variable.lower(): frame = frame[frame["flash_type"] == "CG"] # EarthNetworks validity box (not needed if tiling algorithm) longitude_range = (26, 40) latitude_range = (-12, 4) x_range, y_range = geo.web_mercator(longitude_range, latitude_range) x, y = geo.web_mercator(frame["longitude"], frame["latitude"]) frame["x"] = x frame["y"] = y pixels = 256 canvas = datashader.Canvas( plot_width=pixels, plot_height=pixels, x_range=x_range, y_range=y_range, ) if "density" in state.variable.lower(): # N flashes per pixel agg = canvas.points(frame, "x", "y", datashader.count()) else: frame["since_flash"] = self.since_flash(frame["date"], valid_time) agg = canvas.points(frame, "x", "y", datashader.max("since_flash")) # Note: DataArray objects are not JSON serializable, .values is the # same data cast as a numpy array x = agg.x.values.min() y = agg.y.values.min() dw = agg.x.values.max() - x dh = agg.y.values.max() - y image = np.ma.masked_array( agg.values.astype(np.float), mask=np.isnan(agg.values) ) if "density" in state.variable.lower(): image[image == 0] = np.ma.masked # Remove pixels with no data # Update color_mapper color_mapper = self.color_mappers["image"] if "density" in state.variable.lower(): color_mapper.palette = bokeh.palettes.all_palettes["Spectral"][8] color_mapper.low = 0 color_mapper.high = agg.values.max() else: color_mapper.palette = bokeh.palettes.all_palettes["RdGy"][8] color_mapper.low = 0 color_mapper.high = 60 * 60 # 1 hour # Update tooltips for hover_tool in self.hover_tools["image"]: hover_tool.tooltips = self.tooltips(state.variable) hover_tool.formatters = self.formatters(state.variable) if "density" in state.variable.lower(): units = "events" else: units = "seconds" data = { "x": [x], "y": [y], "dw": [dw], "dh": [dh], "image": [image], } meta_data = { "variable": [state.variable], "date": [valid_time], "units": [units], "window": [window.total_seconds()], } data.update(meta_data) self.sources["image"].data = data
def main(argv=None): config = configure(argv=argv) # Feature toggles if "feature" in config.plugins: features = plugin.call(config.plugins["feature"].entry_point) else: features = config.features data.FEATURE_FLAGS = features # Full screen map viewport = config.default_viewport x_range, y_range = geo.web_mercator(viewport.lon_range, viewport.lat_range) figure = map_figure(x_range, y_range) figures = [figure] for _ in range(2): f = map_figure(figure.x_range, figure.y_range) figures.append(f) figure_row = layers.FigureRow(figures) color_mapper = bokeh.models.LinearColorMapper( low=0, high=1, palette=bokeh.palettes.Plasma[256]) # Convert config to datasets datasets = {} datasets_by_pattern = {} label_to_pattern = {} for group, dataset in zip(config.file_groups, config.datasets): datasets[group.label] = dataset datasets_by_pattern[group.pattern] = dataset label_to_pattern[group.label] = group.pattern # print('\n\n\n\n', datasets, '\n\n\n\n') '''# Lakes for figure in figures: add_feature(figure, data.LAKES, color="lightblue") features = [] for figure in figures: render2 = add_feature(figure, data.LAKES, color="lightblue") 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.CheckboxGroup(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(event): for feature in features: feature.glyph.line_color = new dropdown.on_click(on_change) layers_ui = layers.LayersUI() # Add optional sub-navigators sub_navigators = { key: dataset.navigator() for key, dataset in datasets_by_pattern.items() if hasattr(dataset, "navigator") } navigator = navigate.Navigator(sub_navigators) middlewares = [ keys.navigate, db.InverseCoordinate("pressure"), db.next_previous, db.Controls(navigator), # TODO: Deprecate this middleware colors.palettes, colors.middleware(), presets.Middleware(presets.proxy_storage(config.presets_file)), presets.middleware, layers.middleware, navigator, mws.echo, ] store = redux.Store(forest.reducer, middlewares=middlewares) app = forest.app.Application() app.add_component(forest.components.title.Title()) # Coastlines, borders, lakes and disputed borders view = forest.components.borders.View() for figure in figures: view.add_figure(figure) view.connect(store) border_ui = forest.components.borders.UI() border_ui.connect(store) # Colorbar user interface component = forest.components.ColorbarUI() app.add_component(component) # Add time user interface if config.defaults.timeui: component = forest.components.TimeUI() component.layout = bokeh.layouts.row(component.layout, name="time") app.add_component(component) # Connect MapView orchestration to store opacity_slider = forest.layers.OpacitySlider() source_limits = colors.SourceLimits().connect(store) factory_class = forest.layers.factory(color_mapper, figures, source_limits, opacity_slider) gallery = forest.gallery.Gallery.map_view(datasets, factory_class) gallery.connect(store) # Connect layers controls layers_ui.add_subscriber(store.dispatch) layers_ui.connect(store) # Connect tools controls display_names = { "time_series": "Display Time Series", "profile": "Display Profile", "barc": "BARC Toolkit" } available_features = { k: display_names[k] for k in display_names.keys() if data.FEATURE_FLAGS[k] } tools_panel = tools.ToolsPanel(available_features) tools_panel.connect(store) #barc_toolbar=bokeh.models.tools.Toolbar(tools=barc_tools,logo=None) if data.FEATURE_FLAGS["BARC"]: barc = BARC(figures) tools_panel.layout.children.append(barc.ToolBar()) # Navbar components navbar = Navbar(show_diagram_button=len(available_features) > 0) navbar.connect(store) # Connect tap listener tap_listener = screen.TapListener() tap_listener.connect(store) # Connect figure controls/views if config.defaults.figures.ui: figure_ui = layers.FigureUI(config.defaults.figures.maximum) figure_ui.connect(store) figure_row.connect(store) # Tiling picker if config.use_web_map_tiles: tile_picker = forest.components.TilePicker() for figure in figures: tile_picker.add_figure(figure) tile_picker.connect(store) if not data.FEATURE_FLAGS["multiple_colorbars"]: # Connect color palette controls colors.ColorMapperView(color_mapper).connect(store) color_palette = colors.ColorPalette().connect(store) # Connect limit controllers to store user_limits = colors.UserLimits().connect(store) # Preset if config.defaults.presetui: preset_ui = presets.PresetUI().connect(store) # Connect navigation controls controls = db.ControlView() controls.connect(store) # Add support for a modal dialogue if data.FEATURE_FLAGS["multiple_colorbars"]: view = forest.components.modal.Tabbed() else: view = forest.components.modal.Default() modal = forest.components.Modal(view=view) modal.connect(store) # Connect components to Store app.connect(store) # Set initial state store.dispatch(forest.actions.set_state(config.state).to_dict()) # Pre-select menu choices (if any) for pattern, _ in sub_navigators.items(): state = db.initial_state(navigator, pattern=pattern) store.dispatch(forest.actions.update_state(state).to_dict()) break # Set default time series visibility store.dispatch(tools.on_toggle_tool("time_series", False)) # Set default profile visibility store.dispatch(tools.on_toggle_tool("profile", False)) # Set top-level navigation store.dispatch(db.set_value("patterns", config.patterns)) # Pre-select first map_view layer for label, dataset in datasets.items(): pattern = label_to_pattern[label] for variable in navigator.variables(pattern): spec = { "label": label, "dataset": label, "variable": variable, "active": [0] } store.dispatch(forest.layers.save_layer(0, spec)) break break # Set variable dimensions (needed by modal dialogue) for label, dataset in datasets.items(): pattern = label_to_pattern[label] values = navigator.variables(pattern) store.dispatch(dimension.set_variables(label, values)) # Organise controls/settings layouts = {} layouts["controls"] = [] if config.defaults.figures.ui: layouts["controls"] += [ bokeh.models.Div(text="Layout:"), figure_ui.layout ] layouts["controls"] += [ bokeh.models.Div(text="Navigate:"), controls.layout, bokeh.models.Div(text="Compare:"), layers_ui.layout ] layouts["settings"] = [ bokeh.models.Div(text="Borders, coastlines and lakes:"), border_ui.layout, opacity_slider.layout, ] if not data.FEATURE_FLAGS["multiple_colorbars"]: layouts["settings"].append(color_palette.layout) layouts["settings"].append(user_limits.layout) if config.defaults.presetui: layouts["settings"].append(preset_ui.layout) if config.use_web_map_tiles: layouts["settings"].append(bokeh.models.Div(text="Tiles:")) layouts["settings"].append(tile_picker.layout) tabs = bokeh.models.Tabs(tabs=[ bokeh.models.Panel(child=bokeh.layouts.column(*layouts["controls"]), title="Control"), bokeh.models.Panel(child=bokeh.layouts.column(*layouts["settings"]), title="Settings") ]) tool_figures = {} if data.FEATURE_FLAGS["time_series"]: # 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 gallery = forest.gallery.Gallery.series_view(datasets, series_figure) gallery.connect(store) tool_figures["series_figure"] = series_figure if data.FEATURE_FLAGS["profile"]: # Profile sub-figure widget profile_figure = bokeh.plotting.figure(plot_width=300, plot_height=450, toolbar_location=None, border_fill_alpha=0) profile_figure.toolbar.logo = None profile_figure.y_range.flipped = True gallery = forest.gallery.Gallery.profile_view(datasets, profile_figure) gallery.connect(store) tool_figures["profile_figure"] = profile_figure tool_layout = tools.ToolLayout(**tool_figures) tool_layout.connect(store) for f in figures: f.on_event(bokeh.events.Tap, tap_listener.update_xy) marker = screen.MarkDraw(f).connect(store) control_root = bokeh.layouts.column(tabs, name="controls") # Add key press support key_press = keys.KeyPress() key_press.add_subscriber(store.dispatch) # Add HTML ready support obj = html_ready.HTMLReady(key_press.hidden_button) obj.connect(store) document = bokeh.plotting.curdoc() document.title = "FOREST" document.add_root(control_root) document.add_root( bokeh.layouts.column(tools_panel.layout, tool_layout.layout, width=400, name="series")) for root in navbar.roots: document.add_root(root) for root in app.roots: document.add_root(root) document.add_root(figure_row.layout) document.add_root(key_press.hidden_button) document.add_root(modal.layout)
def main(argv=None): args = parse_args.parse_args(argv) data.AUTO_SHUTDOWN = args.auto_shutdown if len(args.files) > 0: if args.config_file is not None: raise Exception( '--config-file and [FILE [FILE ...]] not compatible') config = cfg.from_files(args.files, args.file_type) else: config = cfg.Config.load(args.config_file, variables=cfg.combine_variables( os.environ, args.variables)) # Feature toggles if "feature" in config.plugins: features = plugin.call(config.plugins["feature"].entry_point) else: features = config.features data.FEATURE_FLAGS = features # Full screen map viewport = config.default_viewport x_range, y_range = geo.web_mercator(viewport.lon_range, viewport.lat_range) figure = map_figure(x_range, y_range) figures = [figure] for _ in range(2): f = map_figure(figure.x_range, figure.y_range) figures.append(f) figure_row = layers.FigureRow(figures) color_mapper = bokeh.models.LinearColorMapper( low=0, high=1, palette=bokeh.palettes.Plasma[256]) # Convert config to datasets datasets = {} datasets_by_pattern = {} label_to_pattern = {} for group in config.file_groups: settings = { "label": group.label, "pattern": group.pattern, "locator": group.locator, "database_path": group.database_path, "directory": group.directory } dataset = drivers.get_dataset(group.file_type, settings) datasets[group.label] = dataset datasets_by_pattern[group.pattern] = dataset label_to_pattern[group.label] = group.pattern layers_ui = layers.LayersUI() # Add optional sub-navigators sub_navigators = { key: dataset.navigator() for key, dataset in datasets_by_pattern.items() if hasattr(dataset, "navigator") } navigator = navigate.Navigator(sub_navigators) middlewares = [ keys.navigate, db.InverseCoordinate("pressure"), db.next_previous, db.Controls(navigator), # TODO: Deprecate this middleware colors.palettes, colors.middleware(), presets.Middleware(presets.proxy_storage(config.presets_file)), presets.middleware, layers.middleware, navigator, mws.echo, ] store = redux.Store(forest.reducer, middlewares=middlewares) app = forest.app.Application() app.add_component(forest.components.title.Title()) # Coastlines, borders, lakes and disputed borders view = forest.components.borders.View() for figure in figures: view.add_figure(figure) view.connect(store) border_ui = forest.components.borders.UI() border_ui.connect(store) # Colorbar user interface component = forest.components.ColorbarUI() app.add_component(component) # Add time user interface if config.defaults.timeui: component = forest.components.TimeUI() component.layout = bokeh.layouts.row(component.layout, name="time") app.add_component(component) # Connect MapView orchestration to store opacity_slider = forest.layers.OpacitySlider() source_limits = colors.SourceLimits().connect(store) factory_class = forest.layers.factory(color_mapper, figures, source_limits, opacity_slider) gallery = forest.layers.Gallery.from_datasets(datasets, factory_class) gallery.connect(store) # Connect layers controls layers_ui.add_subscriber(store.dispatch) layers_ui.connect(store) # Connect tools controls display_names = { "time_series": "Display Time Series", "profile": "Display Profile" } available_features = { k: display_names[k] for k in display_names.keys() if data.FEATURE_FLAGS[k] } tools_panel = tools.ToolsPanel(available_features) tools_panel.connect(store) # Navbar components navbar = Navbar(show_diagram_button=len(available_features) > 0) navbar.connect(store) # Connect tap listener tap_listener = screen.TapListener() tap_listener.connect(store) # Connect figure controls/views if config.defaults.figures.ui: figure_ui = layers.FigureUI(config.defaults.figures.maximum) figure_ui.connect(store) figure_row.connect(store) # Tiling picker if config.use_web_map_tiles: tile_picker = forest.components.TilePicker() for figure in figures: tile_picker.add_figure(figure) tile_picker.connect(store) # Connect color palette controls colors.ColorMapperView(color_mapper).connect(store) color_palette = colors.ColorPalette().connect(store) # Connect limit controllers to store user_limits = colors.UserLimits().connect(store) # Preset if config.defaults.presetui: preset_ui = presets.PresetUI().connect(store) # Connect navigation controls controls = db.ControlView() controls.connect(store) # Add support for a modal dialogue if data.FEATURE_FLAGS["multiple_colorbars"]: view = forest.components.modal.Tabbed() else: view = forest.components.modal.Default() modal = forest.components.Modal(view=view) modal.connect(store) # Connect components to Store app.connect(store) # Set initial state store.dispatch(forest.actions.set_state(config.state).to_dict()) # Pre-select menu choices (if any) for pattern, _ in sub_navigators.items(): state = db.initial_state(navigator, pattern=pattern) store.dispatch(forest.actions.update_state(state).to_dict()) break # Set default time series visibility store.dispatch(tools.on_toggle_tool("time_series", False)) # Set default profile visibility store.dispatch(tools.on_toggle_tool("profile", False)) # Set top-level navigation store.dispatch(db.set_value("patterns", config.patterns)) # Pre-select first map_view layer for label, dataset in datasets.items(): pattern = label_to_pattern[label] for variable in navigator.variables(pattern): spec = { "label": label, "dataset": label, "variable": variable, "active": [0] } store.dispatch(forest.layers.save_layer(0, spec)) break break # Set variable dimensions (needed by modal dialogue) for label, dataset in datasets.items(): pattern = label_to_pattern[label] values = navigator.variables(pattern) store.dispatch(dimension.set_variables(label, values)) # Organise controls/settings layouts = {} layouts["controls"] = [] if config.defaults.figures.ui: layouts["controls"] += [ bokeh.models.Div(text="Layout:"), figure_ui.layout ] layouts["controls"] += [ bokeh.models.Div(text="Navigate:"), controls.layout, bokeh.models.Div(text="Compare:"), layers_ui.layout ] layouts["settings"] = [ border_ui.layout, opacity_slider.layout, color_palette.layout, user_limits.layout, bokeh.models.Div(text="Tiles:"), ] if config.defaults.presetui: layouts["settings"].insert(2, preset_ui.layout) if config.use_web_map_tiles: layouts["settings"].append(tile_picker.layout) tabs = bokeh.models.Tabs(tabs=[ bokeh.models.Panel(child=bokeh.layouts.column(*layouts["controls"]), title="Control"), bokeh.models.Panel(child=bokeh.layouts.column(*layouts["settings"]), title="Settings") ]) tool_figures = {} if data.FEATURE_FLAGS["time_series"]: # 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_view = series.SeriesView.from_groups(series_figure, config.file_groups) series_view.add_subscriber(store.dispatch) series_args = (rx.Stream().listen_to(store).map( series.select_args).filter(lambda x: x is not None).distinct()) series_args.map(lambda a: series_view.render(*a)) series_args.map(print) # Note: map(print) creates None stream tool_figures["series_figure"] = series_figure if data.FEATURE_FLAGS["profile"]: # Profile sub-figure widget profile_figure = bokeh.plotting.figure(plot_width=300, plot_height=450, toolbar_location=None, border_fill_alpha=0) profile_figure.toolbar.logo = None profile_figure.y_range.flipped = True profile_view = profile.ProfileView.from_groups(profile_figure, config.file_groups) profile_view.add_subscriber(store.dispatch) profile_args = (rx.Stream().listen_to(store).map( profile.select_args).filter(lambda x: x is not None).distinct()) profile_args.map(lambda a: profile_view.render(*a)) profile_args.map(print) # Note: map(print) creates None stream tool_figures["profile_figure"] = profile_figure tool_layout = tools.ToolLayout(**tool_figures) tool_layout.connect(store) for f in figures: f.on_event(bokeh.events.Tap, tap_listener.update_xy) marker = screen.MarkDraw(f).connect(store) control_root = bokeh.layouts.column(tabs, name="controls") # Add key press support key_press = keys.KeyPress() key_press.add_subscriber(store.dispatch) # Add HTML ready support obj = html_ready.HTMLReady(key_press.hidden_button) obj.connect(store) document = bokeh.plotting.curdoc() document.title = "FOREST" document.add_root(control_root) document.add_root( bokeh.layouts.column(tools_panel.layout, tool_layout.layout, width=400, name="series")) for root in navbar.roots: document.add_root(root) for root in app.roots: document.add_root(root) document.add_root(figure_row.layout) document.add_root(key_press.hidden_button) document.add_root(modal.layout)
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)
def getRDT(path, lev, type): """ Gets RDT data from the netcdf output of the NWCSAF software :param path: Full path and filename of the netcdf file :param lev: [0,1] Level number. 0 = bottom of the cloud, 1 = top of the cloud (heights vary between clouds) :param type: ['All', 'Centre_Point', 'Polygon', 'Tail_Points', 'Tail_Lines', 'Gridded'] :return: geojson feature collection object for plotting """ # Load the netcdf data ncds = nc.Dataset(path) # Get variable names that have either 'nlevel' or 'recNUM' dimensions varlev = [] # 'nlevel' dimension varrec = [] # 'recNUM' dimension vartraj = [] # 'nbpttraj' Trajectory points for each object for ncv in ncds.variables.keys(): var_dim = ncds.variables[ncv].dimensions if "nlevel" in var_dim: varlev.append(ncv) if ("recNUM" in var_dim) and (ncds.dimensions["recNUM"].size == ncds.variables[ncv].size): varrec.append(ncv) if "nbpttraj" in var_dim: vartraj.append(ncv) allvars = varlev.copy() allvars.extend(varrec) # How many cloud levels have we got? It should only be 2 (cloud top and bottom) dimsize = len(ma.getdata(ncds.variables[varlev[0]][0, :])) # Convert units from the netcdf unitsToRescale = {"Pa": "hPa"} # , 'K': 'degC'} # Get text labels instead of numbers for certain fields fieldsToLookup = [ "PhaseLife", "SeverityType", "SeverityIntensity", "ConvType", "CType", ] # Check the lev is within the dimsize if not lev in np.arange(dimsize): return "Please enter a valid level number (0 or 1)" else: list_to_return = [] if type in ["Polygon", "All"]: # Create list of features to populate features = [] fieldsToLookup = [ "PhaseLife", "SeverityType", "SeverityIntensity", "ConvType", "CType", ] for i in np.arange(ncds.dimensions["recNUM"].size): # do something ypolcoords_ll = getDataOnly(ncds.variables["LatContour"][i, lev, :]) xpolcoords_ll = getDataOnly(ncds.variables["LonContour"][i, lev, :]) xpolcoords, ypolcoords = geo.web_mercator(xpolcoords_ll, ypolcoords_ll) this_poly = Polygon([[(float(coord[0]), float(coord[1])) for coord in zip(xpolcoords, ypolcoords)]]) # Get the properties for this polygon pol_props = {} for var in allvars: if var in varlev: thisdata = ncds.variables[var][i, lev] else: thisdata = ncds.variables[var][i] thisdata_scaled = convert_values(thisdata, ncds.variables[var]) datatype = ncds.variables[var].datatype if var in fieldsToLookup: try: thisdata_scaled = fieldValueLUT(var, int(thisdata)) datatype = "string" except: continue update_json(pol_props, var, thisdata_scaled, datatype) features.append(Feature(geometry=this_poly, properties=pol_props)) feature_collection = FeatureCollection(features) list_to_return.append(json.dumps(feature_collection)) if type in ["Tail_Lines", "All"]: # Create an empty dictionary mydict_tl = get_empty_feature_dict("Tail_Lines") # Loop through features for i in np.arange(ncds.dimensions["recNUM"].size): # Loop through all items in mydict_tl for k in mydict_tl.keys(): try: thisdata, units = descale_rdt( k, getDataOnly(ncds.variables[k][:])) mydict_tl[k].append(thisdata) except: # Do nothing at the moment with the xs and ys if not k in ["xs", "ys"]: mydict_tl[k].append(None) else: continue lats = getDataOnly(ncds.variables["LatTrajCellCG"][i, :]) lons = getDataOnly(ncds.variables["LonTrajCellCG"][i, :]) xs, ys = geo.web_mercator(lons, lats) mydict_tl["xs"].append(xs) mydict_tl["ys"].append(ys) list_to_return.append(mydict_tl) if type in ["Tail_Points", "All"]: # Create an empty dictionary mydict_tp = get_empty_feature_dict("Tail_Points") for i in np.arange(ncds.dimensions["recNUM"].size): # do something npts = len(getDataOnly(ncds.variables["LonTrajCellCG"][i])) mykeys = [k for k in mydict_tp.keys() if k not in ["x", "y"]] for k in mykeys: thisdata = getDataOnly(ncds.variables[k][i]).tolist() try: if len(thisdata) == 1: thisdata = thisdata[0] except: pass if isinstance(thisdata, list) and (npts == len(thisdata)): datalist, units = descale_rdt(k, thisdata) mydict_tp[k].extend(datalist) else: # Repeat the data value npts times datalist = [i for i in itertools.repeat(thisdata, npts)] thisdata, units = descale_rdt(k, datalist) mydict_tp[k].extend(datalist) # Some records don't have any data. Mostly happens for ExpanRateTraj if len(thisdata) == 0: datalist = [i for i in itertools.repeat("-", npts)] mydict_tp[k].extend(datalist) lats = getDataOnly(ncds.variables["LatTrajCellCG"][i, :]) lons = getDataOnly(ncds.variables["LonTrajCellCG"][i, :]) x, y = geo.web_mercator(lons, lats) mydict_tp["x"].extend(x) mydict_tp["y"].extend(y) list_to_return.append(mydict_tp) # Do specific things for each feature type if type in ["Centre_Point", "All"]: # Create an empty dictionary mydict_cp = get_empty_feature_dict("Centre_Point") for i in np.arange(ncds.dimensions["recNUM"].size): # Get the Point features lat = ncds.variables["LatG"][i, lev] lon = ncds.variables["LonG"][i, lev] x1, y1 = geo.web_mercator(lon, lat) mydict_cp["x1"].extend(x1) mydict_cp["y1"].extend(y1) mykeys = [ k for k in mydict_cp.keys() if not (("x" in k) or ("y" in k)) ] for k in mykeys: try: if k in varlev: thisdata = ncds.variables[k][i, lev] else: thisdata = ncds.variables[k][i] thisdata, units = descale_rdt( k, thisdata) # May not need to do this mydict_cp[k].append(thisdata) except: mydict_cp[k].append(None) # Now calculate future point and line try: speed = float(ncds.variables["MvtSpeed"][i]) except ValueError: speed = 0 try: direction = float(ncds.variables["MvtDirection"][i]) except ValueError: direction = 0 mydict_cp = make_arrow(mydict_cp, lon, lat, speed, direction) list_to_return.append(mydict_cp) if len(list_to_return) == 1: return list_to_return[0] elif len(list_to_return) > 1: return tuple(list_to_return) else: return "Nothing to return"