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)
Example #2
0
    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)