def compose_overlay_plot(self, x_range: Optional[Sequence[float]] = (-1.6, -1.2), y_range: Optional[Sequence[float]] = (50.8, 51.05)) \ -> Union[Overlay, Element]: """ Compose all generated HoloViews layers in self.data_layers into a single overlay plot. Overlaid in a first-on-the-bottom manner. If plot bounds has moved outside of data bounds, generate more as required. :param tuple x_range: (min, max) longitude range in EPSG:4326 coordinates :param tuple y_range: (min, max) latitude range in EPSG:4326 coordinates :returns: overlay plot of stored layers """ from itertools import chain try: if not self._preload_complete: # If layers aren't preloaded yet just return the map tiles self._progress_callback('Still preloading layer data...') plot = self._base_tiles else: # Construct box around requested bounds bounds_poly = make_bounds_polygon(x_range, y_range) raster_shape = self._get_raster_dimensions( bounds_poly, self.raster_resolution_m) # Ensure bounds are small enough to render without OOM or heat death of universe if bounds_poly.area < 0.2: from time import time t0 = time() self._progress_bar_callback(10) self.generate_layers(bounds_poly, raster_shape) self._progress_bar_callback(50) plot = Overlay([ res[0] for res in self._generated_data_layers.values() ]) print("Generated all layers in ", time() - t0) if self.annotation_layers: import matplotlib.pyplot as mpl plot = Overlay([ res[0] for res in self._generated_data_layers.values() ]) raw_datas = [ res[2] for res in self._generated_data_layers.values() ] raster_indices = dict( Longitude=np.linspace(x_range[0], x_range[1], num=raster_shape[0]), Latitude=np.linspace(y_range[0], y_range[1], num=raster_shape[1])) raster_grid = np.zeros( (raster_shape[1], raster_shape[0]), dtype=np.float64) for res in self._generated_data_layers.values(): layer_raster_grid = res[1] if layer_raster_grid is not None: nans = np.isnan(layer_raster_grid) layer_raster_grid[nans] = 0 raster_grid += layer_raster_grid raster_grid = np.flipud(raster_grid) raster_indices['Latitude'] = np.flip( raster_indices['Latitude']) self._progress_callback('Annotating Layers...') res = jl.Parallel( n_jobs=1, verbose=1, backend='threading')( jl.delayed(layer.annotate)(raw_datas, ( raster_indices, raster_grid)) for layer in self.annotation_layers) annotation_overlay = Overlay( [annot for annot in res if annot is not None]) plot = Overlay( [self._base_tiles, plot, annotation_overlay]).collate() else: plot = Overlay([self._base_tiles, plot]).collate() self._progress_bar_callback(90) else: self._progress_callback('Area too large to render!') if not self._generated_data_layers: plot = self._base_tiles else: plot = Overlay([ self._base_tiles, *list(self._generated_data_layers.values()) ]) layers = [] for layer in chain(self.data_layers, self.annotation_layers): d = {'key': layer.key} if hasattr(layer, '_colour'): d.update(colour=layer._colour) if hasattr(layer, '_osm_tag'): d.update(dataTag=layer._osm_tag) layers.append(d) self._progress_callback("Rendering new map...") self._update_callback( list(chain(self.data_layers, self.annotation_layers))) except Exception as e: # Catch-all to prevent plot blanking out and/or crashing app # Just display map tiles in case this was transient import traceback traceback.print_exc() print(e) plot = self._base_tiles return plot.opts(width=self.plot_size[0], height=self.plot_size[1], tools=self.tools, active_tools=self.active_tools)
def compose_overlay_plot(self, x_range: Optional[Sequence[float]] = (-1.6, -1.2), y_range: Optional[Sequence[float]] = (50.8, 51.05)) \ -> Union[Overlay, Element]: """ Compose all generated HoloViews layers in self.data_layers into a single overlay plot. Overlaid in a first-on-the-bottom manner. If plot bounds has moved outside of data bounds, generate more as required. :param tuple x_range: (min, max) longitude range in EPSG:4326 coordinates :param tuple y_range: (min, max) latitude range in EPSG:4326 coordinates :returns: overlay plot of stored layers """ try: if not self._preload_complete: # If layers aren't preloaded yet just return the map tiles self._progress_callback('Still preloading layer data...') plot = self._base_tiles else: # Construct box around requested bounds bounds_poly = make_bounds_polygon(x_range, y_range) raster_shape = self._get_raster_dimensions( bounds_poly, self.raster_resolution_m) # Ensure bounds are small enough to render without OOM or heat death of universe if (raster_shape[0] * raster_shape[1]) < 7e5: from time import time t0 = time() self._progress_bar_callback(10) # TODO: This will give multiple data layers, these need to be able to fed into their relevent pathfinding layers for annlayer in self.annotation_layers: new_layer = FatalityRiskLayer( 'Fatality Risk', ac=annlayer.aircraft['name']) self.add_layer(new_layer) self.remove_duplicate_layers() self._progress_bar_callback(20) self.generate_layers(bounds_poly, raster_shape) self._progress_bar_callback(50) plt_lyr = list(self._generated_data_layers)[0] plot = Overlay([self._generated_data_layers[plt_lyr][0]]) print("Generated all layers in ", time() - t0) if self.annotation_layers: plot = Overlay( [self._generated_data_layers[plt_lyr][0]]) res = [] prog_bar = 50 for dlayer in self.data_layers: raster_indices = dict( Longitude=np.linspace(x_range[0], x_range[1], num=raster_shape[0]), Latitude=np.linspace(y_range[0], y_range[1], num=raster_shape[1])) raw_data = [ self._generated_data_layers[dlayer.key][2] ] raster_grid = np.sum([ remove_raster_nans( self._generated_data_layers[dlayer.key][1]) ], axis=0) raster_grid = np.flipud(raster_grid) raster_indices['Latitude'] = np.flip( raster_indices['Latitude']) for alayer in self.annotation_layers: if alayer.aircraft == dlayer.ac_dict: self._progress_bar_callback(prog_bar) prog_bar += 40 / len( self.annotation_layers) self._progress_callback( f'Finding a path for {alayer.aircraft["name"]}' ) res.append( alayer.annotate( raw_data, (raster_indices, raster_grid))) self._progress_callback('Plotting paths') self._progress_bar_callback(90) # res = jl.Parallel(n_jobs=1, verbose=1, backend='threading')( # jl.delayed(layer.annotate)(raw_datas, (raster_indices, raster_grid)) for layer in # self.annotation_layers ) plot = Overlay([ self._base_tiles, plot, *[annot for annot in res if annot is not None] ]).collate() else: plot = Overlay([self._base_tiles, plot]).collate() self._progress_bar_callback(90) else: self._progress_callback('Area too large to render!') if not self._generated_data_layers: plot = self._base_tiles else: plot = Overlay([ self._base_tiles, *list(self._generated_data_layers.values()) ]) self._update_layer_list() self._progress_callback("Rendering new map...") except Exception as e: # Catch-all to prevent plot blanking out and/or crashing app # Just display map tiles in case this was transient import traceback traceback.print_exc() self._progress_callback( f'Plotting failed with the following error: {e}. Please attempt to re-generate the plot' ) print(e) plot = self._base_tiles return plot.opts(width=self.plot_size[0], height=self.plot_size[1], tools=self.tools, active_tools=self.active_tools)