def setUp(self) -> None:
        super().setUp()

        self.hour = 17
        self.serialise = False
        self.test_bound_coords = [-1.5, 50.87, -1.3, 51]
        # self.test_bound_coords = [-1.55, 50.745, -1.3, 51]
        self.resolution = 30
        self.test_bounds = make_bounds_polygon(
            (self.test_bound_coords[0], self.test_bound_coords[2]),
            (self.test_bound_coords[1], self.test_bound_coords[3]))

        self._setup_aircraft()

        ps = PlotServer()
        ps.set_time(self.hour)
        self.raster_shape = ps._get_raster_dimensions(self.test_bounds,
                                                      self.resolution)
        ps.data_layers = [TemporalPopulationEstimateLayer('tpe')]

        [
            layer.preload_data()
            for layer in chain(ps.data_layers, ps.annotation_layers)
        ]
        ps.generate_layers(self.test_bounds, self.raster_shape)
        self.raster_grid = np.flipud(
            np.sum([
                remove_raster_nans(res[1])
                for res in ps._generated_data_layers.values()
                if res[1] is not None
            ],
                   axis=0))
        self.raster_shape = self.raster_grid.shape
        del ps
Esempio n. 2
0
def make(min_lat, max_lat, min_lon, max_lon, start_lat, start_lon, end_lat,
         end_lon, aircraft, failure_prob, output_path, resolution, hour,
         altitude, airspeed, wind_direction, wind_speed, algo, algo_args):
    """
    Path minimising the fatality risk posed.

    Generate a path minimising the fatality risk posed by a specified aircraft in the specified
    bounds. The path is output as a GeoJSON file in the form of a LineString of EPSG:4326 coordinates in (lon, lat) order
    from start to end.

    If an aircraft config json file is not specified, a default aircraft with reasonable parameters is used.

    All coordinates should be in decimal degrees and form a non degenerate polygon.

    """
    bounds = make_bounds_polygon((min_lon, max_lon), (min_lat, max_lat))
    pop_grid = _make_pop_grid(bounds, hour, resolution)
    if not aircraft:
        aircraft = _setup_default_aircraft()
    else:
        aircraft = _import_aircraft(aircraft)
    strike_grid, v_is = _make_strike_grid(aircraft, airspeed, altitude,
                                          failure_prob, pop_grid, resolution,
                                          wind_direction, wind_speed)
    res = _make_fatality_grid(aircraft, strike_grid, v_is)

    raster_shape = res.shape
    raster_indices = dict(Longitude=np.linspace(min_lon,
                                                max_lon,
                                                num=raster_shape[0]),
                          Latitude=np.linspace(min_lat,
                                               max_lat,
                                               num=raster_shape[1]))
    start_x, start_y = snap_coords_to_grid(raster_indices, start_lon,
                                           start_lat)
    end_x, end_y = snap_coords_to_grid(raster_indices, end_lon, end_lat)

    env = GridEnvironment(res, diagonals=False)
    if algo == 'ra*':
        algo = RiskGridAStar()
    elif algo == 'ga':
        raise NotImplementedError("GA CLI not implemented yet")
        algo = GeneticAlgorithm([], **algo_args)
    path = algo.find_path(env, Node((start_x, start_y)), Node((end_x, end_y)))

    if not path:
        print('Path not found')
        return 1

    snapped_path = []
    for node in path:
        lat = raster_indices['Latitude'][node.position[1]]
        lon = raster_indices['Longitude'][node.position[0]]
        snapped_path.append((lon, lat))
    snapped_path = sg.LineString(snapped_path)
    dataframe = gpd.GeoDataFrame({
        'geometry': [snapped_path]
    }).set_crs('EPSG:4326')
    dataframe.to_file(os.path.join(output_path, f'path.geojson'),
                      driver='GeoJSON')
Esempio n. 3
0
def fatality(min_lat, max_lat, min_lon, max_lon, aircraft, failure_prob,
             output_path, resolution, hour, altitude, airspeed, wind_direction,
             wind_speed):
    """
    Fatality Risk map

    Generate a geotiff raster file of the probability of ground fatality with a specified aircraft in the specified
    bounds.

    If an aircraft config json file is not specified, a default aircraft with reasonable parameters is used.

    All coordinates should be in decimal degrees and form a non degenerate polygon.

    """
    bounds = make_bounds_polygon((min_lon, max_lon), (min_lat, max_lat))
    pop_grid = _make_pop_grid(bounds, hour, resolution)

    if not aircraft:
        aircraft = _setup_default_aircraft()
    else:
        aircraft = _import_aircraft(aircraft)

    strike_grid, v_is = _make_strike_grid(aircraft, airspeed, altitude,
                                          failure_prob, pop_grid, resolution,
                                          wind_direction, wind_speed)

    res = _make_fatality_grid(aircraft, strike_grid, v_is)

    out_name = f'fatality_{hour}h.tif'

    _write_geotiff(max_lat, max_lon, min_lat, min_lon, out_name, output_path,
                   res)
 def setUp(self) -> None:
     super().setUp()
     if not hasattr(self, 'layer'):
         return
     self.layer.preload_data()
     self.test_bounds = make_bounds_polygon((-1.5, -1.3), (50.87, 51))
     self.raster_shape = (500, 500)
     if not hasattr(self, 'hour'):
         self.hour = 13
Esempio n. 5
0
 def setUp(self) -> None:
     super().setUp()
     if not hasattr(self, 'layer'):
         return
     import os
     os.chdir(
         os.sep.join(
             (os.path.dirname(os.path.realpath(__file__)), '..', '..')))
     self.layer.preload_data()
     self.test_bounds = make_bounds_polygon((-1.5, -1.3), (50.87, 51))
     self.raster_shape = (500, 500)
     if not hasattr(self, 'hour'):
         self.hour = 13
Esempio n. 6
0
def pop_density(min_lat, max_lat, min_lon, max_lon, output_path, resolution,
                hour):
    """
    Temporal Population Density map

    Generate a geotiff raster file of the population density in the specified bounds.

    All coordinates should be in decimal degrees and form a non degenerate polygon.

    """
    bounds = make_bounds_polygon((min_lon, max_lon), (min_lat, max_lat))
    raster_grid = _make_pop_grid(bounds, hour, resolution)

    out_name = f'pop_density_{hour}h.tif'
    _write_geotiff(max_lat, max_lon, min_lat, min_lon, out_name, output_path,
                   raster_grid)
Esempio n. 7
0
def make(min_lat, max_lat, min_lon, max_lon, start_lat, start_lon, end_lat,
         end_lon, aircraft, failure_prob, output_path, resolution, hour,
         altitude, airspeed, wind_direction, wind_speed, algo, algo_args):
    """
    Path minimising the fatality risk posed.

    Generate a path minimising the fatality risk posed by a specified aircraft in the specified
    bounds. The path is output as a GeoJSON file in the form of a LineString of EPSG:4326 coordinates in (lon, lat) order
    from start to end.

    If an aircraft config json file is not specified, a default aircraft with reasonable parameters is used.

    All coordinates should be in decimal degrees and form a non degenerate polygon.

    """
    import geopandas as gpd
    import os

    bounds = make_bounds_polygon((min_lon, max_lon), (min_lat, max_lat))
    pop_grid = make_pop_grid(bounds, hour, resolution)
    if not aircraft:
        aircraft = _setup_default_aircraft()
    else:
        aircraft = _import_aircraft(aircraft)
    strike_grid, v_is = make_strike_grid(aircraft, airspeed, altitude,
                                         failure_prob, pop_grid, resolution,
                                         wind_direction, wind_speed)
    res = make_fatality_grid(aircraft, strike_grid, v_is)

    snapped_path = make_path(res,
                             bounds, (start_lat, start_lon),
                             (end_lat, end_lon),
                             algo=algo)
    if snapped_path:
        dataframe = gpd.GeoDataFrame({
            'geometry': [snapped_path]
        }).set_crs('EPSG:4326')
        dataframe.to_file(os.path.join(output_path, f'path.geojson'),
                          driver='GeoJSON')
    else:
        print('Path could not be found')
Esempio n. 8
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)
                    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:
                        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.sum([
                            remove_raster_nans(res[1])
                            for res in self._generated_data_layers.values()
                            if res[1] is not None
                        ],
                                             axis=0)
                        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)

                        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()
            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)
Esempio n. 9
0
 def test_bounds_polygon_2arg(self):
     res = make_bounds_polygon((1, 5), (1, 5))
     poly = sg.Polygon([(1, 1), (1, 5), (5, 5), (5, 1)])
     # __eq__ and .equals are not the same for sg objects!
     self.assertTrue(res.equals(poly))
Esempio n. 10
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)