def get_values(self, rectangle): """Return an array covering the given rectangle. Increasing x/y array indexes correspond to increasing value in the projected space. Cell size is the one of the tile. """ assert self.x_delta > 0 ((x_min, x_max), (y_min, y_max)) = rectangle if self.y_delta > 0: # get indexes of sub-array xi_min, yi_min = self.projected_to_raster((x_min, y_min)) xi_max, yi_max = self.projected_to_raster((x_max, y_max)) # returns the subarray subarray = self.data[xi_min:xi_max + 1, yi_min:yi_max + 1] return GeoData(subarray, *self.raster_to_projected((xi_min, yi_min)), self.x_delta, self.y_delta, projection=self.geoprojection) else: # our internal data structure is inversed on y-axis # get indexes of sub-array xi_min, yi_min = self.projected_to_raster((x_min, y_max)) xi_max, yi_max = self.projected_to_raster((x_max, y_min)) # returns the sub-array, inversed on the y-axis subarray = self.data[xi_min:xi_max + 1, yi_min:yi_max + 1][..., ::-1] return GeoData(subarray, *self.raster_to_projected((xi_min, yi_max)), self.x_delta, -self.y_delta, projection=self.geoprojection)
def test_right_append(self): gd_right = GeoData(np.array([[31, 32], [41, 42]]), 2, 0, 1, 1) res = self.gd.append_right(gd_right) self.assertEqual(res.data[0, 0], 11) self.assertEqual(res.data[1, 1], 22) self.assertEqual(res.data[0, 1], 12) self.assertEqual(res.data[1, 0], 21) self.assertEqual(res.data[3, 0], 41) self.assertEqual(res.data.shape, (4, 2))
def fig_observation_contour(base_propagation: FirePropagation, cells: ty.Mapping[ty.Tuple[int, int], float], cmap="Greens_r", binary=True, gdd=None) -> display.GeoDataDisplay: gd = GeoData.full_like(base_propagation.prop_data, np.inf) for cell, time in cells.items(): gd.data["ignition"][cell] = time if not binary else 1.0 if not gdd: gdd = display.GeoDataDisplay.pyplot_figure(gd, frame=(0., 0.)) gdd.draw_ignition_shade(cmap=cmap, geodata=gd) return gdd
def test_geodata_conversion(self): from fire_rs.geodata.environment import World world = World() gd = world.get_elevation([[475060.0, 485060], [6200074.0, 6210074]]) raster = gd.as_cpp_raster() print(raster) gd2 = GeoData.from_cpp_raster(raster, "elevation_cpp") print(gd2[10, 10][0]) self.assertAlmostEqual(gd[10, 10][0], gd2[10, 10][0]) self.assertAlmostEqual(gd.x_offset, gd2.x_offset) self.assertAlmostEqual(gd.y_offset, gd2.y_offset) self.assertEqual(gd.data.shape, gd2.data.shape) print(gd2)
def make_utility_map(firemap: GeoData, flight_window: TimeWindow = TimeWindow(-np.inf, np.inf), layer="ignition", output_layer="utility") -> GeoData: """Compute a utility map from a wildfire map""" gradient = rate_of_spread_map(firemap, layer=layer, output_layer="ros") grad_array = gradient["ros"] grad_array[grad_array >= np.inf] = np.NaN utility = 1 - (grad_array - np.nanmin(grad_array)) / ( np.nanmax(grad_array) - np.nanmin(grad_array)) utility[np.isnan(utility)] = 0. utility[(firemap[layer] < flight_window.start) | (firemap[layer] > flight_window.end)] = 0. return firemap.clone(data_array=utility, dtype=[(output_layer, 'float64')])
class WorldTest(unittest.TestCase): def setUp(self): self.gd = GeoData(np.array([[11, 12], [21, 22]]), 0, 0, 1, 1) def test_access(self): self.assertEqual(self.gd.data[0, 0], 11) self.assertEqual(self.gd.data[1, 1], 22) self.assertEqual(self.gd.data[0, 1], 12) self.assertEqual(self.gd.data[1, 0], 21) def test_right_append(self): gd_right = GeoData(np.array([[31, 32], [41, 42]]), 2, 0, 1, 1) res = self.gd.append_right(gd_right) self.assertEqual(res.data[0, 0], 11) self.assertEqual(res.data[1, 1], 22) self.assertEqual(res.data[0, 1], 12) self.assertEqual(res.data[1, 0], 21) self.assertEqual(res.data[3, 0], 41) self.assertEqual(res.data.shape, (4, 2)) def test_bottom_append(self): gd_right = GeoData(np.array([[31, 32], [41, 42]]), 0, 2, 1, 1) res = self.gd.append_bottom(gd_right) self.assertEqual(res.data.shape, (2, 4)) def test_split(self): res = self.gd.split(2, 2) assert all([(1, 1) == t.data.shape for t in res]) # recreate initial array from the split ones combined = res[0].append_right(res[1]).append_bottom( res[2].append_right(res[3])) np.testing.assert_allclose(self.gd.data, combined.data) def test_subset(self): res = self.gd.subset(Area(1, 1, 0, 1)) self.assertEqual(res.data.shape, (1, 2))
def plot_plan_with_background(plan: Plan, geodatadisplay: gdd, time_range, output_options_plot): """Plot a plan trajectories with background geographic information in a geodata display.""" # Draw background layers for layer in output_options_plot['background']: if layer == 'elevation_shade': geodatadisplay.draw_elevation_shade( with_colorbar=output_options_plot.get('colorbar', True), layer='elevation') if layer == 'elevation_planning_shade': geodatadisplay.draw_elevation_shade( with_colorbar=output_options_plot.get('colorbar', True), layer='elevation_planning') elif layer == 'ignition_shade': geodatadisplay.draw_ignition_shade( with_colorbar=output_options_plot.get('colorbar', True)) elif layer == 'observedcells': raise NotImplementedError() # geodatadisplay.TrajectoryDisplayExtension.draw_observation_map( # planner.expected_observed_map(layer_name="expected_observed"), # layer='expected_observed', color='green', alpha=0.9) # geodatadisplay.TrajectoryDisplayExtension.draw_observation_map( # planner.expected_ignited_map(layer_name="expected_ignited"), # layer='expected_ignited', color='red', alpha=0.9) elif layer == 'ignition_contour': try: geodatadisplay.draw_ignition_contour(with_labels=True) except ValueError as e: logger.exception("ValueError while drawing ignition contour") elif layer == 'wind_quiver': geodatadisplay.draw_wind_quiver() elif layer == 'utilitymap': geodatadisplay.TrajectoryDisplayExtension.draw_utility_shade( geodata=GeoData.from_cpp_raster( plan.utility_map(), 'utility', projection=geodatadisplay.geodata.projection), layer='utility', vmin=0., vmax=1.) plot_plan_trajectories(plan, geodatadisplay, layers=output_options_plot["foreground"], time_range=time_range)
def geodata_from_raster_msg(msg: Raster, layer: str, invert=False): """Deserialize a Raster msg into a GeoData object. if layer is "elevation", the raster is inverted """ array = np.fromiter(zip(msg.data), dtype=[(layer, 'float64')]) array.resize((msg.metadata.x_width, msg.metadata.y_height)) # FIXME (Workaround) invert elevation rasters as they are shown inverted in the GUI if layer == "elevation": array = array[..., ::-1] if invert: array = array[..., ::-1] return GeoData(array, msg.metadata.x_offset, msg.metadata.y_offset, msg.metadata.cell_width, msg.metadata.cell_width, projection=msg.metadata.epsg)
def plot_sr_with_background(sr: 'planning.up.SearchResult', geodatadisplay, time_range, output_options_plot, plan: 'Union[str, int]' = 'final'): """Plot a plan trajectories with background geographic information in a geodata display.""" # Draw background layers for layer in output_options_plot['background']: if layer == 'elevation_shade': geodatadisplay.draw_elevation_shade( with_colorbar=output_options_plot.get('colorbar', True), layer='elevation') if layer == 'elevation_planning_shade': geodatadisplay.draw_elevation_shade( with_colorbar=output_options_plot.get('colorbar', True), layer='elevation_planning') elif layer == 'ignition_shade': geodatadisplay.draw_ignition_shade( with_colorbar=output_options_plot.get('colorbar', True)) # elif layer == 'observedcells': # geodatadisplay.TrajectoryDisplayExtension.draw_observation_map( # planner.expected_observed_map(layer_name="expected_observed"), # layer='expected_observed', color='green', alpha=0.9) # geodatadisplay.TrajectoryDisplayExtension.draw_observation_map( # planner.expected_ignited_map(layer_name="expected_ignited"), # layer='expected_ignited', color='red', alpha=0.9) elif layer == 'ignition_contour': try: geodatadisplay.draw_ignition_contour(with_labels=True) except ValueError as e: logger.exception("ValueError while drawing ignition contour") elif layer == 'wind_quiver': geodatadisplay.draw_wind_quiver() elif layer == 'utilitymap': geodatadisplay.TrajectoryDisplayExtension.draw_utility_shade( geodata=GeoData.from_cpp_raster(sr.final_plan().utility_map(), 'utility'), layer='utility', vmin=0., vmax=1.) plot_plan_trajectories(sr.plan(plan), geodatadisplay, layers=output_options_plot["foreground"], time_range=time_range)
def observed(self) -> GeoData: return GeoData.from_cpp_raster(self._gfmapper.observed, self.observed_fire_layer)
def pr(cpp_raster, blocking=False): # plots a cpp raster return GeoData.from_cpp_raster(cpp_raster, "xx").plot(blocking=blocking)
# output_format="pdf" area = ((481000.0, 484000.0), (6211000.0, 6213500.0)) expected_env = propagation.Environment(area, wind_speed=5., wind_dir=0.) now = datetime.datetime.now().timestamp() ignition_point = TimedPoint(area[0][0] + 1000.0, area[1][0] + 1000.0, now) expected_fire = propagation.propagate_from_points( expected_env, ignition_point, now + datetime.timedelta(hours=2).total_seconds()) env = propagation.Environment(area, wind_speed=6., wind_dir=0.) fire = propagation.propagate_from_points( env, ignition_point, now + datetime.timedelta(hours=2).total_seconds()) # Create fire perimeter geodata fire_perimeter = GeoData.full_like(fire.prop_data.slice("ignition"), np.nan) # Create contour with scikit-image contours = skimage.measure.find_contours(fire.prop_data.data["ignition"], now + datetime.timedelta(hours=1).total_seconds()) # Draw contour in the geodata for contour in contours: for pt_i in range(len(contour)): if pt_i < len(contour) / 3: continue rr, cc = skimage.draw.line(*np.asarray(contour[pt_i - 1], dtype=int), *np.asarray(contour[pt_i], dtype=int)) fire_perimeter.data[rr, cc] = fire.prop_data.data["ignition"][rr, cc] contours = skimage.measure.find_contours(fire.prop_data.data["ignition"], now + datetime.timedelta(minutes=30).total_seconds()) # Draw contour in the geodata
def observed(self) -> 'GeoData': return GeoData.from_cpp_raster(self._gfmapper.observed, self.output_fire_layer)
def test_bottom_append(self): gd_right = GeoData(np.array([[31, 32], [41, 42]]), 0, 2, 1, 1) res = self.gd.append_bottom(gd_right) self.assertEqual(res.data.shape, (2, 4))
def make_fire_data(fire_map: GeoData, elevation_map: GeoData, fire_map_layer: str = 'ignition', elevation_map_layer: str = 'elevation'): return FireData(fire_map.as_cpp_raster(fire_map_layer), elevation_map.as_cpp_raster(elevation_map_layer))
def utility_map(self, layer_name='utility') -> 'GeoData': return GeoData.from_cpp_raster( self._searchresult.final_plan().utility_map(), layer_name)
def setUp(self): self.gd = GeoData(np.array([[11, 12], [21, 22]]), 0, 0, 1, 1)
def run_benchmark(scenario: Scenario, save_directory: str, instance_name: str, output_options_plot: ty.Mapping[str, ty.Any], output_options_planning: ty.Mapping[str, ty.Any], snapshots: bool, vns_name: str, output_options_data: ty.Mapping[str, ty.Any]): _logger.info("Starting benchmark instance %s", instance_name) # Fetch scenario environment data with additional elevation mode for planning env = PlanningEnvironment( scenario.area, wind_speed=scenario.wind_speed, wind_dir=scenario.wind_direction, planning_elevation_mode=output_options_planning['elevation_mode'], discrete_elevation_interval=DISCRETE_ELEVATION_INTERVAL, world=fire_rs.geodata.environment.World( landcover_to_fuel_remap=fire_rs.geodata.environment. EVERYTHING_FUELMODEL_REMAP)) # Propagate fires in the environment propagation_end_time = scenario.time_window_end + 60 * 10 _logger.debug("Propagating fire ignitions %s until %s", str(scenario.ignitions), str(propagation_end_time)) prop = propagation.propagate_from_points(env, scenario.ignitions, until=propagation_end_time) ignitions = prop.ignitions() # Transform altitude of UAV bases from agl (above ground level) to absolute for f in scenario.flights: base_h = 0. # If flat, start at the default segment insertion h if output_options_planning['elevation_mode'] != 'flat': base_h = base_h + env.raster["elevation"][env.raster.array_index( (f.base_waypoint.x, f.base_waypoint.y))] # The new WP is (old_x, old_y, old_z + elevation[old_x, old_y], old_dir) f.base_waypoint = Waypoint(f.base_waypoint.x, f.base_waypoint.y, base_h, f.base_waypoint.dir) conf = { 'min_time': scenario.time_window_start, 'max_time': scenario.time_window_end, 'save_every': 0, 'save_improvements': snapshots, 'vns': vns_configurations[vns_name] } conf['vns']['configuration_name'] = vns_name # Define the flight window flight_window = TimeWindow(scenario.time_window_start, scenario.time_window_end) # Create utility map utility = make_utility_map(ignitions, flight_window, layer='ignition', output_layer='utility') utility_cpp = utility.as_cpp_raster('utility') # Make a FireData object fire_data = make_fire_data(ignitions, env.raster, elevation_map_layer='elevation_planning') # Create an initial plan initial_plan = Plan(instance_name, [f.as_cpp() for f in scenario.flights], fire_data, flight_window, utility_cpp) # Set up a Planner object from the initial Plan pl = Planner(initial_plan, vns_configurations[vns_name]) pl.save_improvements = snapshots pl.save_every = 0 search_result = pl.compute_plan() final_plan = search_result.final_plan() # Propagation was running more time than desired in order to reduce edge effect. # Now we need to filter out-of-range ignition times. Crop range is rounded up to the next # 10-minute mark (minus 1). This makes color bar ranges and fire front contour plots nicer ignitions['ignition'][ignitions['ignition'] > \ int(scenario.time_window_end / 60. + 5) * 60. - 60] = _DBL_MAX # Representation of unburned cells using max double is not suitable for display, # so those values must be converted to NaN ignitions_nan = ignitions['ignition'].copy() ignitions_nan[ignitions_nan == _DBL_MAX] = np.nan first_ignition = np.nanmin(ignitions_nan) last_ignition = np.nanmax(ignitions_nan) # Create the geodatadisplay object & extensions that are going to be used geodatadisplay = GeoDataDisplay.pyplot_figure( env.raster.combine(ignitions), frame=(0, 0)) geodatadisplay.add_extension(TrajectoryDisplayExtension, (None, ), {}) plot_plan_with_background(final_plan, geodatadisplay, (first_ignition, last_ignition), output_options_plot) # Save the picture filepath = os.path.join( save_directory, instance_name + "." + str(output_options_plot.get('format', 'png'))) _logger.info("Saving as figure as: %s", str(filepath)) geodatadisplay.axes.get_figure().set_size_inches( *output_options_plot.get('size', (15, 10))) geodatadisplay.axes.get_figure().savefig(filepath, dpi=output_options_plot.get( 'dpi', 150), bbox_inches='tight') # Save rasters if output_options_data['save_rasters']: raster_data_dir = os.path.join(save_directory, instance_name + "_data") if not os.path.exists(raster_data_dir): os.makedirs(raster_data_dir) env.raster.write_to_file( os.path.join( raster_data_dir, ".".join(("_".join( (instance_name, "elevation_planning")), 'tif'))), 'elevation_planning') env.raster.write_to_file( os.path.join( raster_data_dir, ".".join(("_".join( (instance_name, "wind_velocity")), 'tif'))), 'wind_velocity') env.raster.write_to_file( os.path.join( raster_data_dir, ".".join(("_".join( (instance_name, "wind_angle")), 'tif'))), 'wind_angle') prop.ignitions().write_to_file(os.path.join( raster_data_dir, ".".join(("_".join( (instance_name, "ignition")), 'tif'))), 'ignition', nodata=np.inf) u_initial_map = GeoData.from_cpp_raster(initial_plan.utility_map(), "utility", projection=utility.projection) u_initial_map.write_to_file( os.path.join( raster_data_dir, ".".join(("_".join( (instance_name, "utility_initial")), 'tif'))), 'utility') u_final_map = GeoData.from_cpp_raster(final_plan.utility_map(), "utility", projection=utility.projection) u_final_map.write_to_file( os.path.join( raster_data_dir, ".".join(("_".join( (instance_name, "utility_final")), 'tif'))), 'utility') # print([o.as_tuple() for o in final_plan.observations()]) # Log planned trajectories metadata for i, t in enumerate(final_plan.trajectories()): _logger.debug("traj #{} duration: {} / {}".format( i, t.duration(), t.conf.max_flight_time)) # save metadata to file with open(os.path.join(save_directory, instance_name + ".json"), "w") as metadata_file: import json parsed = json.loads(search_result.metadata()) parsed["benchmark_id"] = instance_name parsed["date"] = datetime.datetime.now().isoformat() metadata_file.write(json.dumps(parsed, indent=4)) matplotlib.pyplot.close(geodatadisplay.axes.get_figure()) # If intermediate plans are available, save them for i, plan in enumerate(search_result.intermediate_plans): geodatadisplay = GeoDataDisplay.pyplot_figure( env.raster.combine(ignitions)) geodatadisplay.add_extension(TrajectoryDisplayExtension, (None, ), {}) plot_plan_with_background(plan, geodatadisplay, (first_ignition, last_ignition), output_options_plot) i_plan_dir = os.path.join(save_directory, instance_name) if not os.path.exists(i_plan_dir): os.makedirs(i_plan_dir) filepath = os.path.join( i_plan_dir, str(i) + "." + str(output_options_plot.get('format', 'png'))) _logger.info("Saving intermediate plan as: %s", str(filepath)) geodatadisplay.axes.get_figure().set_size_inches( *output_options_plot.get('size', (15, 10))) geodatadisplay.axes.get_figure().savefig(filepath, dpi=output_options_plot.get( 'dpi', 150), bbox_inches='tight') matplotlib.pyplot.close(geodatadisplay.axes.get_figure()) del search_result
def make_flat_utility_map(firemap: GeoData, flight_window: TimeWindow = TimeWindow(-np.inf, np.inf), layer="ignition", output_layer="utility") -> GeoData: """Create a constant utility map""" utility = firemap.clone(fill_value=1., dtype=[(output_layer, 'float64')]) utility[output_layer][(firemap[layer] < flight_window.start) | (firemap[layer] > flight_window.end)] = 0. return utility
def firemap(self) -> 'GeoData': return GeoData.from_cpp_raster(self._gfmapper.firemap, self.output_fire_layer)
def empty_firemap(base_raster: GeoData, layer: str = "ignition") -> GeoData: """Create an empty fire map from a base raster""" return base_raster.clone(fill_value=np.inf, dtype=[(layer, 'float64')])
# Obtain Obs fire using a firemapper over a fake ground truth. # In normal conditions, Obs fire should be provided by neptus elevation_draster = pl_env.raster.as_cpp_raster('elevation') ignition_draster = ignitions.as_cpp_raster() gfmapper = fmapping.GhostFireMapper( op.FireData(ignition_draster, elevation_draster)) # Suposing we follow exactly the planned path # executed_path is a tuple with a list of waypoints and a list of the corresponding time executed_path = search_result.final_plan().trajectories( )[0].sampled_with_time(step_size=10) executed_path_wp = executed_path[0] executed_path_t = executed_path[1] # type: 'op.DRaster' obs_fire_raster = gfmapper.observed_firemap(executed_path_wp, executed_path_t, flight_confs_cpp[0].uav) obs_fire = GeoData.from_cpp_raster(obs_fire_raster, 'ignition') gdd = GeoDataDisplay.pyplot_figure(obs_fire) gdd.add_extension(TrajectoryDisplayExtension, (None, )) gdd.draw_ignition_contour(geodata=ignitions, cmap=mcm.Greens) gdd.draw_ignition_shade() plot_plan_trajectories(search_result.final_plan(), gdd) print("fin") # TODO: Reconstruct fire perimeter from Obs fire # search_result = observation_planner.replan_after_time(later)