def create_bokeh_figure( corners, lon_limits, lat_limits, base_url="figs/", base_height=800, image_alpha=0.8, title="GOES GeoColor Imagery at ", ): img_args, x_range, y_range, scale = compute_image_locations_ranges( corners, lon_limits, lat_limits) map_fig = figure( plot_width=int(scale * base_height), plot_height=base_height, x_axis_type="mercator", y_axis_type="mercator", x_range=x_range, y_range=y_range, title=title, toolbar_location="right", sizing_mode="scale_width", name="map_fig", ) hover = HoverTool( names=['sites'], tooltips=[("Site", "@name"), ('Value', '@value of @capacity @units Max'), ('Time', '@time')], ) map_fig.add_tools(hover) map_fig.xaxis.axis_label = ( "Data from https://registry.opendata.aws/noaa-goes/. Map tiles from Stamen Design. " # NOQA "Plot generated with Bokeh by UA HAS Renewable Power Forecasting Group." ) map_fig.xaxis.axis_label_text_font_size = "7pt" map_fig.xaxis.axis_line_alpha = 0 slider = Slider( title="GOES Image", start=0, end=1, value=1, name="timeslider", sizing_mode="scale_width", ) play_buttons = RadioButtonGroup( labels=["\u25B6", "\u25FC", "\u27F3"], active=1, name="play_buttons", sizing_mode="scale_width", ) speed_source = ColumnDataSource(data=dict(speed=[config.PLAY_SPEED]), name='speed_source') faster_button = Button(label="\u2795", sizing_mode='fixed', width=30, height=30, name="faster_button") slower_button = Button(label="\u2796", sizing_mode='fixed', width=30, height=30, name="slower_button") play_button_row = row([play_buttons, faster_button, slower_button], sizing_mode='scale_width', name='all_play_buttons') fig_source = ColumnDataSource(data=dict(url=[]), name="figsource", id="figsource") adapter = CustomJS( args=dict(slider=slider, fig_source=fig_source, base_url=base_url, title=map_fig.title, max_images=config.MAX_IMAGES), code=""" const result = {url: []} const urls = cb_data.response var pnglen = 0 var len = urls.length var start = 0 if (len > max_images) { start = len - max_images } for (i=start; i<len; i++) { var name = urls[i]['name']; if (name.endsWith('.png')) { result.url.push(base_url + name) pnglen += 1 } } slider.end = Math.max(pnglen - 1, 1) var lasturl = result.url[pnglen - 1] cb_obj.tags = [lasturl] // first run if (fig_source.data['url'].length == 0) { fig_source.data['url'][0] = lasturl fig_source.tags = [''] fig_source.change.emit() slider.value = slider.end } slider.change.emit() return result """, ) url_source = AjaxDataSource(data_url=base_url, polling_interval=10000, adapter=adapter) url_source.method = "GET" # url_source.if_modified = True pt_adapter = CustomJS(code=""" const result = {x: [], y: [], name: [], capacity: [], value: [], time: [], units: []} const pts = cb_data.response for (i=0; i<pts.length; i++) { result.x.push(pts[i]['x']) result.y.push(pts[i]['y']) result.name.push(pts[i]['name']) result.capacity.push(pts[i]['capacity']) result.units.push(pts[i]['units']) result.time.push(pts[i]['last_time']) result.value.push(pts[i]['last_value']) } return result """) pt_source = AjaxDataSource(data_url="metadata.json", polling_interval=int(1e5), adapter=pt_adapter) pt_source.method = "GET" slider_callback = CustomJS( args=dict(fig_source=fig_source, url_source=url_source), code=""" if ('url' in url_source.data && cb_obj.value < url_source.data['url'].length){ var inp_url = url_source.data['url'][cb_obj.value]; fig_source.data['url'][0] = inp_url; fig_source.tags = [inp_url]; fig_source.change.emit(); } if (cb_obj.value == cb_obj.end) { cb_obj.tags = [1] } else { cb_obj.tags = [0] } """, ) newest_callback = CustomJS(args=dict(fig_source=fig_source, slider=slider), code=""" if (slider.tags[0] == 1){ if (slider.value != slider.end) { slider.value = slider.end slider.change.emit() } fig_source.data['url'][0] = cb_obj.tags[0] fig_source.tags = [cb_obj.tags[0]]; fig_source.change.emit(); } """) title_callback = CustomJS( args=dict(title=map_fig.title, base_title=title), code=""" var url = cb_obj.data['url'][0] var date = new Date(url.split('/').pop().split('_').pop().split('.')[0]) title.text = base_title + date title.change.emit() """, ) faster_callback = CustomJS(args=dict(speed_source=speed_source, speed_incr=config.PLAY_SPEED_INCR), code=""" var old = speed_source.data['speed'][0] var newspeed = old - speed_incr if (newspeed > 0){ speed_source.data = {"speed": [newspeed]} speed_source.change.emit() } """) slower_callback = CustomJS(args=dict(speed_source=speed_source, speed_incr=config.PLAY_SPEED_INCR), code=""" var old = speed_source.data['speed'][0] var newspeed = old + speed_incr speed_source.data = {"speed": [newspeed]} speed_source.change.emit() """) change_speed_callback = CustomJS(args=dict(slider=slider, play_buttons=play_buttons, pause=config.RESTART_PAUSE), code=""" var timeoutSet = false; function fromstart(){ timeoutSet = false if (play_buttons.active == 0){ slider.value = 0 slider.change.emit() } } function advance() { if (slider.value < slider.end) { slider.value += 1 slider.change.emit() } else if (!timeoutSet){ setTimeout(fromstart, pause) timeoutSet = true; } } if (play_buttons.active == 0){ var id = play_buttons._id clearInterval(id) id = setInterval(advance, cb_obj.data['speed'][0]) play_buttons._id = id } """) play_callback = CustomJS( args=dict(slider=slider, speed_source=speed_source, pause=config.RESTART_PAUSE), code=""" function stop() { var id = cb_obj._id clearInterval(id) cb_obj.active = 1 } var timeoutSet = false; function fromstart(){ timeoutSet = false if (cb_obj.active == 0){ slider.value = 0 slider.change.emit() } } function advance() { if (slider.value < slider.end) { slider.value += 1 slider.change.emit() } else if (!timeoutSet){ setTimeout(fromstart, pause) timeoutSet = true; } } function start() { var id = setInterval(advance, speed_source.data['speed'][0]) cb_obj._id = id } if (cb_obj.active == 0) { start() } else if (cb_obj.active == 2) { stop() slider.value = 0 slider.change.emit() } else { stop() } """, ) # ajaxdatasource to nginx list of files as json possibly on s3 # write metadata for plants to file, serve w/ nginx # new goes image triggers lambda to process sns -> sqs -> lambda map_fig.add_tile(STAMEN_TONER) map_fig.image_url(url="url", global_alpha=image_alpha, source=fig_source, **img_args) if config.SERVICE_AREA is not None: geo_source = GeoJSONDataSource( geojson=getattr(areas, config.SERVICE_AREA.upper())) map_fig.patches(xs='xs', ys='ys', source=geo_source, fill_alpha=0, line_alpha=0.9, line_color=config.BLUE) map_fig.cross(x="x", y="y", size=12, fill_alpha=0.9, source=pt_source, color=config.RED, name='sites') fig_source.js_on_change("tags", title_callback) slider.js_on_change("value", slider_callback) url_source.js_on_change("tags", newest_callback) play_buttons.js_on_change("active", play_callback) faster_button.js_on_click(faster_callback) slower_button.js_on_click(slower_callback) speed_source.js_on_change("data", change_speed_callback) doc = curdoc() for thing in (map_fig, play_button_row, slider): doc.add_root(thing) doc.title = "GOES Image Viewer" return doc
def create_bokeh_figure( corners, lon_limits, lat_limits, base_url="figs/", base_height=800, image_alpha=0.8, title="GOES GeoColor Imagery at ", ): img_args, x_range, y_range, scale = compute_image_locations_ranges( corners, lon_limits, lat_limits) map_fig = figure( plot_width=int(scale * base_height), plot_height=base_height, x_axis_type="mercator", y_axis_type="mercator", x_range=x_range, y_range=y_range, title=title, toolbar_location="right", sizing_mode="scale_width", name="map_fig", tooltips=[("Site", "@name")], ) slider = Slider( title="GOES Image", start=0, end=100, value=0, name="timeslider", sizing_mode="scale_width", ) play_buttons = RadioButtonGroup( labels=["\u25B6", "\u25FC", "\u27F3"], active=1, name="play_buttons", sizing_mode="scale_width", ) fig_source = ColumnDataSource(data=dict(url=[]), name="figsource", id="figsource") adapter = CustomJS( args=dict(slider=slider, fig_source=fig_source, base_url=base_url, title=map_fig.title), code=""" const result = {url: []} const urls = cb_data.response var pnglen = 0; for (i=0; i<urls.length; i++) { var name = urls[i]['name']; if (name.endsWith('.png')) { result.url.push(base_url + name) pnglen += 1 } } slider.end = Math.max(pnglen - 1, 1) slider.change.emit() if (fig_source.data['url'].length == 0) { fig_source.data['url'][0] = result.url[0] fig_source.tags = [''] fig_source.change.emit() } return result """, ) url_source = AjaxDataSource(data_url=base_url, polling_interval=10000, adapter=adapter) url_source.method = "GET" # url_source.if_modified = True pt_adapter = CustomJS(code=""" const result = {x: [], y: [], name: []} const pts = cb_data.response for (i=0; i<pts.length; i++) { result.x.push(pts[i]['x']) result.y.push(pts[i]['y']) result.name.push(pts[i]['name']) } return result """) pt_source = AjaxDataSource( data_url=base_url + "/metadata.json", polling_interval=int(1e5), adapter=pt_adapter, ) pt_source.method = "GET" callback = CustomJS( args=dict(fig_source=fig_source, url_source=url_source), code=""" if (cb_obj.value < url_source.data['url'].length){ var inp_url = url_source.data['url'][cb_obj.value]; fig_source.data['url'][0] = inp_url; fig_source.tags = [cb_obj.value]; fig_source.change.emit(); } """, ) title_callback = CustomJS( args=dict(title=map_fig.title, base_title=title), code=""" var url = cb_obj.data['url'][0] var date = url.split('/').pop().split('_').pop().split('.')[0] title.text = base_title + date title.change.emit() """, ) play_callback = CustomJS( args=dict(slider=slider, play_speed=config.PLAY_SPEED), code=""" function stop() { var id = cb_obj._id clearInterval(id) cb_obj.active = 1 } function advance() { if (slider.value < slider.end) { slider.value += 1 } else { slider.value = 0 } slider.change.emit() } function start() { var id = setInterval(advance, play_speed) cb_obj._id = id } if (cb_obj.active == 0) { start() } else if (cb_obj.active == 2) { stop() slider.value = 0 slider.change.emit() } else { stop() } """, ) # ajaxdatasource to nginx list of files as json possibly on s3 # write metadata for plants to file, serve w/ nginx # new goes image triggers lambda to process sns -> sqs -> lambda map_fig.add_tile(STAMEN_TONER) map_fig.image_url(url="url", global_alpha=image_alpha, source=fig_source, **img_args) fig_source.js_on_change("tags", title_callback) map_fig.cross(x="x", y="y", size=12, fill_alpha=0.9, source=pt_source, color=config.RED) slider.js_on_change("value", callback) play_buttons.js_on_change("active", play_callback) doc = curdoc() for thing in (map_fig, play_buttons, slider): doc.add_root(thing) doc.title = "GOES Image Viewer" return doc