def test_buffer_simple_polygon_large(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer distance is larger. """ this_buffer_polygon_object = polygons.buffer_simple_polygon( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_polygon_object) this_buffer_vertex_x_metres, this_buffer_vertex_y_metres = ( polygons.merge_exterior_and_holes( this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) self.assertTrue( numpy.allclose(this_buffer_vertex_x_metres, VERTEX_X_METRES_LARGE_BUFFER, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_y_metres, VERTEX_Y_METRES_LARGE_BUFFER, atol=TOLERANCE))
def test_polygon_object_to_vertex_arrays(self): """Ensures correct output from polygon_object_to_vertex_arrays.""" this_vertex_dict = polygons.polygon_object_to_vertex_arrays( POLYGON_OBJECT_XY) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.EXTERIOR_X_COLUMN], EXTERIOR_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.EXTERIOR_Y_COLUMN], EXTERIOR_VERTEX_Y_METRES, atol=TOLERANCE)) self.assertTrue( len(this_vertex_dict[polygons.HOLE_X_COLUMN]) == NUM_HOLES) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_X_COLUMN][0], HOLE1_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_Y_COLUMN][0], HOLE1_VERTEX_Y_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_X_COLUMN][1], HOLE2_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_Y_COLUMN][1], HOLE2_VERTEX_Y_METRES, atol=TOLERANCE))
def plot_storm_outlines( storm_object_table, axes_object, basemap_object, line_width=DEFAULT_POLYGON_WIDTH, line_colour=DEFAULT_POLYGON_COLOUR, line_style='solid'): """Plots all storm objects in the table (as unfilled polygons). :param storm_object_table: See doc for `storm_tracking_io.write_file`. :param axes_object: Will plot on these axes (instance of `matplotlib.axes._subplots.AxesSubplot`). :param basemap_object: Will use this object (instance of `mpl_toolkits.basemap.Basemap`) to convert between x-y and lat-long coords. :param line_width: Width of each polygon. :param line_colour: Colour of each polygon. :param line_style: Line style for each polygon. """ line_colour_tuple = plotting_utils.colour_from_numpy_to_tuple(line_colour) num_storm_objects = len(storm_object_table.index) for i in range(num_storm_objects): this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( storm_object_table[tracking_utils.LATLNG_POLYGON_COLUMN].values[i] ) these_x_coords_metres, these_y_coords_metres = basemap_object( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN] ) axes_object.plot( these_x_coords_metres, these_y_coords_metres, color=line_colour_tuple, linestyle=line_style, linewidth=line_width )
def test_buffer_simple_polygon_large_inclusive(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer is large and inclusive (includes original polygon). """ this_buffered_polygon_object = polygons.buffer_simple_polygon( vertex_x_metres=EXTERIOR_VERTEX_X_METRES, vertex_y_metres=EXTERIOR_VERTEX_Y_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffered_polygon_object) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], LARGE_BUFFER_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], LARGE_BUFFER_VERTEX_Y_METRES, atol=TOLERANCE)) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_X_COLUMN]) == 0) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN]) == 0)
def _project_storms_latlng_to_xy(storm_object_table, projection_object): """Projects storm objects from lat-long to x-y coordinates. This method projects both centroids and outlines. V = number of vertices in a given storm object :param storm_object_table: pandas DataFrame created by _read_storm_tracks. :param projection_object: Instance of `pyproj.Proj`, created by _init_azimuthal_equidistant_projection. :return: storm_object_table: Same as input, but with additional columns listed below. storm_object_table.centroid_x_metres: x-coordinate of centroid. storm_object_table.centroid_y_metres: y-coordinate of centroid. storm_object_table.vertices_x_metres: length-V numpy array with x- coordinates of vertices. storm_object_table.vertices_y_metres: length-V numpy array with y- coordinates of vertices. """ (centroids_x_metres, centroids_y_metres) = projections.project_latlng_to_xy( storm_object_table[tracking_io.CENTROID_LAT_COLUMN].values, storm_object_table[tracking_io.CENTROID_LNG_COLUMN].values, projection_object=projection_object) nested_array = storm_object_table[[ tracking_io.STORM_ID_COLUMN, tracking_io.STORM_ID_COLUMN ]].values.tolist() argument_dict = { CENTROID_X_COLUMN: centroids_x_metres, CENTROID_Y_COLUMN: centroids_y_metres, VERTICES_X_COLUMN: nested_array, VERTICES_Y_COLUMN: nested_array } storm_object_table = storm_object_table.assign(**argument_dict) num_storm_objects = len(storm_object_table.index) for i in range(num_storm_objects): this_vertex_dict_latlng = (polygons.polygon_object_to_vertex_arrays( storm_object_table[ tracking_io.POLYGON_OBJECT_LATLNG_COLUMN].values[i])) (storm_object_table[VERTICES_X_COLUMN].values[i], storm_object_table[VERTICES_Y_COLUMN].values[i]) = ( projections.project_latlng_to_xy( this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN], this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], projection_object=projection_object)) return storm_object_table
def test_vertex_arrays_to_polygon_object(self): """Ensures correct output from vertex_arrays_to_polygon_object. This is not strictly a unit test. vertex_arrays_to_polygon_object is used to convert to a polygon object, and then polygon_object_to_vertex_arrays is used to convert back to vertex arrays. The output of polygon_object_to_vertex_arrays is compared with the input to vertex_arrays_to_polygon_object, and both sets of vertex arrays must be equal. """ this_polygon_object = polygons.vertex_arrays_to_polygon_object( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, hole_x_coords_list=[HOLE1_VERTEX_X_METRES, HOLE2_VERTEX_X_METRES], hole_y_coords_list=[HOLE1_VERTEX_Y_METRES, HOLE2_VERTEX_Y_METRES]) this_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_polygon_object) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.EXTERIOR_X_COLUMN], EXTERIOR_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.EXTERIOR_Y_COLUMN], EXTERIOR_VERTEX_Y_METRES, atol=TOLERANCE)) self.assertTrue( len(this_vertex_dict[polygons.HOLE_X_COLUMN]) == NUM_HOLES) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_X_COLUMN][0], HOLE1_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_Y_COLUMN][0], HOLE1_VERTEX_Y_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_X_COLUMN][1], HOLE2_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_vertex_dict[polygons.HOLE_Y_COLUMN][1], HOLE2_VERTEX_Y_METRES, atol=TOLERANCE))
def _remove_far_away_storms(warning_polygon_object_latlng, storm_object_table): """Removes storms that are far away from a warning polygon. :param warning_polygon_object_latlng: See doc for `_link_one_warning`. :param storm_object_table: Same. :return: storm_object_table: Same as input but with fewer rows. """ this_vertex_dict = polygons.polygon_object_to_vertex_arrays( warning_polygon_object_latlng) warning_latitudes_deg = this_vertex_dict[polygons.EXTERIOR_Y_COLUMN] warning_longitudes_deg = this_vertex_dict[polygons.EXTERIOR_X_COLUMN] unique_primary_id_strings = numpy.unique( storm_object_table[tracking_utils.PRIMARY_ID_COLUMN].values) good_indices = [] for i in range(len(unique_primary_id_strings)): these_rows = numpy.where( storm_object_table[tracking_utils.PRIMARY_ID_COLUMN].values == unique_primary_id_strings[i])[0] these_latitudes_deg = storm_object_table[ tracking_utils.CENTROID_LATITUDE_COLUMN].values[these_rows] these_longitudes_deg = storm_object_table[ tracking_utils.CENTROID_LONGITUDE_COLUMN].values[these_rows] these_latitude_flags = numpy.logical_and( these_latitudes_deg >= numpy.min(warning_latitudes_deg) - 1., these_latitudes_deg <= numpy.max(warning_latitudes_deg) + 1.) these_longitude_flags = numpy.logical_and( these_longitudes_deg >= numpy.min(warning_longitudes_deg) - 1., these_longitudes_deg <= numpy.max(warning_longitudes_deg) + 1.) these_coord_flags = numpy.logical_and(these_latitude_flags, these_longitude_flags) if not numpy.any(these_coord_flags): continue good_indices.append(i) unique_primary_id_strings = [ unique_primary_id_strings[k] for k in good_indices ] return storm_object_table.loc[storm_object_table[ tracking_utils.PRIMARY_ID_COLUMN].isin(unique_primary_id_strings)]
def plot_storm_outline_filled(basemap_object, axes_object, polygon_object_latlng, line_colour=DEFAULT_POLYGON_LINE_COLOUR, line_width=DEFAULT_POLYGON_LINE_WIDTH, fill_colour=DEFAULT_POLYGON_FILL_COLOUR, opacity=DEFAULT_POLYGON_FILL_OPACITY): """Plots storm outline (or buffer around storm outline) as filled polygon. :param basemap_object: Instance of `mpl_toolkits.basemap.Basemap`. :param axes_object: Instance of `matplotlib.axes._subplots.AxesSubplot`. :param polygon_object_latlng: `shapely.geometry.Polygon` object with vertices in lat-long coordinates. :param line_colour: Colour of polygon edge (in any format accepted by `matplotlib.colors`). :param line_width: Width of polygon edge. :param fill_colour: Colour of polygon interior. :param opacity: Opacity of polygon fill (in range 0...1). """ vertex_dict = polygons.polygon_object_to_vertex_arrays( polygon_object_latlng) exterior_x_coords_metres, exterior_y_coords_metres = basemap_object( vertex_dict[polygons.EXTERIOR_X_COLUMN], vertex_dict[polygons.EXTERIOR_Y_COLUMN]) num_holes = len(vertex_dict[polygons.HOLE_X_COLUMN]) x_coords_by_hole_metres = [None] * num_holes y_coords_by_hole_metres = [None] * num_holes for i in range(num_holes): x_coords_by_hole_metres[i], y_coords_by_hole_metres[ i] = basemap_object(vertex_dict[polygons.HOLE_X_COLUMN][i], vertex_dict[polygons.HOLE_Y_COLUMN][i]) polygon_object_xy = polygons.vertex_arrays_to_polygon_object( exterior_x_coords=exterior_x_coords_metres, exterior_y_coords=exterior_y_coords_metres, hole_x_coords_list=x_coords_by_hole_metres, hole_y_coords_list=y_coords_by_hole_metres) polygon_patch = PolygonPatch(polygon_object_xy, lw=line_width, ec=line_colour, fc=fill_colour, alpha=opacity) axes_object.add_patch(polygon_patch)
def plot_storm_outline_unfilled( basemap_object, axes_object, polygon_object_latlng, exterior_colour=DEFAULT_POLYGON_LINE_COLOUR, exterior_line_width=DEFAULT_POLYGON_LINE_WIDTH, hole_colour=DEFAULT_POLYGON_HOLE_LINE_COLOUR, hole_line_width=DEFAULT_POLYGON_HOLE_LINE_WIDTH): """Plots storm outline (or buffer around storm outline) as unfilled polygon. :param basemap_object: Instance of `mpl_toolkits.basemap.Basemap`. :param axes_object: Instance of `matplotlib.axes._subplots.AxesSubplot`. :param polygon_object_latlng: `shapely.geometry.Polygon` object with vertices in lat-long coordinates. :param exterior_colour: Colour for exterior of polygon (in any format accepted by `matplotlib.colors`). :param exterior_line_width: Line width for exterior of polygon (real positive number). :param hole_colour: Colour for holes in polygon (in any format accepted by `matplotlib.colors`). :param hole_line_width: Line width for holes in polygon (real positive number). """ vertex_dict = polygons.polygon_object_to_vertex_arrays( polygon_object_latlng) exterior_x_coords_metres, exterior_y_coords_metres = basemap_object( vertex_dict[polygons.EXTERIOR_X_COLUMN], vertex_dict[polygons.EXTERIOR_Y_COLUMN]) axes_object.plot(exterior_x_coords_metres, exterior_y_coords_metres, color=exterior_colour, linestyle='solid', linewidth=exterior_line_width) num_holes = len(vertex_dict[polygons.HOLE_X_COLUMN]) for i in range(num_holes): these_x_coords_metres, these_y_coords_metres = basemap_object( vertex_dict[polygons.HOLE_X_COLUMN][i], vertex_dict[polygons.HOLE_Y_COLUMN][i]) axes_object.plot(these_x_coords_metres, these_y_coords_metres, color=hole_colour, linestyle='solid', linewidth=hole_line_width)
def _get_bounding_boxes(outlook_or_warning_table): """Returns lat-long bounding box for each polygon (outlook or warning). :param outlook_or_warning_table: pandas DataFrame from file created by convert_spc_outlook.py or convert_warning_polygons.py. :return: outlook_or_warning_table: Same as input but with extra columns listed below. outlook_or_warning_table['min_latitude_deg']: Minimum latitude (deg N) in polygon. outlook_or_warning_table['max_latitude_deg']: Max latitude (deg N) in polygon. outlook_or_warning_table['min_longitude_deg']: Minimum longitude (deg E) in polygon. outlook_or_warning_table['max_longitude_deg']: Max longitude (deg E) in polygon. """ num_polygons = len(outlook_or_warning_table.index) min_latitudes_deg = numpy.full(num_polygons, numpy.nan) max_latitudes_deg = numpy.full(num_polygons, numpy.nan) min_longitudes_deg = numpy.full(num_polygons, numpy.nan) max_longitudes_deg = numpy.full(num_polygons, numpy.nan) for i in range(num_polygons): this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( outlook_or_warning_table[POLYGON_COLUMN].values[i]) min_latitudes_deg[i] = numpy.min( this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) max_latitudes_deg[i] = numpy.max( this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) min_longitudes_deg[i] = numpy.min( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN]) max_longitudes_deg[i] = numpy.max( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN]) return outlook_or_warning_table.assign( **{ MIN_LATITUDE_COLUMN: min_latitudes_deg, MAX_LATITUDE_COLUMN: max_latitudes_deg, MIN_LONGITUDE_COLUMN: min_longitudes_deg, MAX_LONGITUDE_COLUMN: max_longitudes_deg })
def erode_boundary(latitudes_deg, longitudes_deg, erosion_distance_metres): """Erodes boundary. Erosion is the same thing as applying a negative buffer distance. The new boundary will be contained inside the old boundary. :param latitudes_deg: See doc for `_check_boundary`. :param longitudes_deg: Same. :param erosion_distance_metres: Erosion distance. :return: latitudes_deg: Eroded version of input. :return: longitudes_deg: Eroded version of input. """ longitudes_deg = _check_boundary(latitudes_deg=latitudes_deg, longitudes_deg=longitudes_deg) error_checking.assert_is_greater(erosion_distance_metres, 0.) polygon_object_latlng = polygons.vertex_arrays_to_polygon_object( exterior_x_coords=longitudes_deg, exterior_y_coords=latitudes_deg) polygon_object_xy, projection_object = polygons.project_latlng_to_xy( polygon_object_latlng=polygon_object_latlng) polygon_object_xy = polygon_object_xy.buffer( -erosion_distance_metres, join_style=shapely.geometry.JOIN_STYLE.round) if 'MultiPolygon' in str(type(polygon_object_xy)): polygon_object_xy = list(polygon_object_xy)[0] polygon_object_latlng = polygons.project_xy_to_latlng( polygon_object_xy_metres=polygon_object_xy, projection_object=projection_object) polygon_dict_latlng = polygons.polygon_object_to_vertex_arrays( polygon_object_latlng) latitudes_deg = polygon_dict_latlng[polygons.EXTERIOR_Y_COLUMN] longitudes_deg = polygon_dict_latlng[polygons.EXTERIOR_X_COLUMN] longitudes_deg = _check_boundary(latitudes_deg=latitudes_deg, longitudes_deg=longitudes_deg) return latitudes_deg, longitudes_deg
def test_buffer_simple_polygon_exclusive(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer is exclusive (does not include original polygon). """ this_buffer_polygon_object = polygons.buffer_simple_polygon( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, min_buffer_dist_metres=SMALL_BUFFER_DIST_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_polygon_object) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], EXCLUSIVE_BUFFER_EXTERIOR_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], EXCLUSIVE_BUFFER_EXTERIOR_Y_METRES, atol=TOLERANCE)) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_X_COLUMN]) == 1) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN]) == 1) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.HOLE_X_COLUMN][0], EXCLUSIVE_BUFFER_HOLE_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN][0], EXCLUSIVE_BUFFER_HOLE_Y_METRES, atol=TOLERANCE))
def test_buffer_simple_polygon_nested(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer is nested (large distance used to create exterior, small distance used to create hole). """ this_buffer_polygon_object = polygons.buffer_simple_polygon( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, min_buffer_dist_metres=SMALL_BUFFER_DIST_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_polygon_object) this_buffer_vertex_x_metres, this_buffer_vertex_y_metres = ( polygons.merge_exterior_and_holes( this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) self.assertTrue( numpy.allclose(this_buffer_vertex_x_metres, VERTEX_X_METRES_NESTED_BUFFER, atol=TOLERANCE, equal_nan=True)) self.assertTrue( numpy.allclose(this_buffer_vertex_y_metres, VERTEX_Y_METRES_NESTED_BUFFER, atol=TOLERANCE, equal_nan=True))
def make_buffers_around_storm_objects( storm_object_table, min_distances_metres, max_distances_metres): """Creates one or more distance buffers around each storm object. N = number of storm objects B = number of buffers around each storm object V = number of vertices in a given buffer :param storm_object_table: N-row pandas DataFrame with the following columns. storm_object_table.storm_id: String ID for storm cell. storm_object_table.polygon_object_latlng: Instance of `shapely.geometry.Polygon`, containing vertices of storm object in lat-long coordinates. :param min_distances_metres: length-B numpy array of minimum buffer distances. If min_distances_metres[i] is NaN, the storm object is included in the [i]th buffer, so the [i]th buffer is inclusive. If min_distances_metres[i] is a real number, the storm object is *not* included in the [i]th buffer, so the [i]th buffer is exclusive. :param max_distances_metres: length-B numpy array of maximum buffer distances. Must be all real numbers (no NaN). :return: storm_object_table: Same as input, but with B additional columns. Each additional column (listed below) contains a `shapely.geometry.Polygon` instance for each storm object. Each `shapely.geometry.Polygon` instance contains the lat-long vertices of one distance buffer around one storm object. storm_object_table.polygon_object_latlng_buffer_<D>m: For an inclusive buffer of D metres around the storm. storm_object_table.polygon_object_latlng_buffer_<d>_<D>m: For an exclusive buffer of d...D metres around the storm. """ error_checking.assert_is_geq_numpy_array( min_distances_metres, 0., allow_nan=True) error_checking.assert_is_numpy_array( min_distances_metres, num_dimensions=1) num_buffers = len(min_distances_metres) error_checking.assert_is_geq_numpy_array( max_distances_metres, 0., allow_nan=False) error_checking.assert_is_numpy_array( max_distances_metres, exact_dimensions=numpy.array([num_buffers])) for j in range(num_buffers): if numpy.isnan(min_distances_metres[j]): continue error_checking.assert_is_greater( max_distances_metres[j], min_distances_metres[j], allow_nan=False) num_storm_objects = len(storm_object_table.index) centroid_latitudes_deg = numpy.full(num_storm_objects, numpy.nan) centroid_longitudes_deg = numpy.full(num_storm_objects, numpy.nan) for i in range(num_storm_objects): this_centroid_object = storm_object_table[ POLYGON_OBJECT_LATLNG_COLUMN].values[0].centroid centroid_latitudes_deg[i] = this_centroid_object.y centroid_longitudes_deg[i] = this_centroid_object.x (global_centroid_lat_deg, global_centroid_lng_deg ) = geodetic_utils.get_latlng_centroid( latitudes_deg=centroid_latitudes_deg, longitudes_deg=centroid_longitudes_deg) projection_object = projections.init_azimuthal_equidistant_projection( global_centroid_lat_deg, global_centroid_lng_deg) object_array = numpy.full(num_storm_objects, numpy.nan, dtype=object) argument_dict = {} buffer_column_names = [''] * num_buffers for j in range(num_buffers): buffer_column_names[j] = distance_buffer_to_column_name( min_distances_metres[j], max_distances_metres[j]) argument_dict.update({buffer_column_names[j]: object_array}) storm_object_table = storm_object_table.assign(**argument_dict) for i in range(num_storm_objects): orig_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( storm_object_table[POLYGON_OBJECT_LATLNG_COLUMN].values[i]) (orig_vertex_x_metres, orig_vertex_y_metres) = projections.project_latlng_to_xy( orig_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN], orig_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], projection_object=projection_object) for j in range(num_buffers): buffer_polygon_object_xy = polygons.buffer_simple_polygon( orig_vertex_x_metres, orig_vertex_y_metres, min_buffer_dist_metres=min_distances_metres[j], max_buffer_dist_metres=max_distances_metres[j]) buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( buffer_polygon_object_xy) (buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN]) = ( projections.project_xy_to_latlng( buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], projection_object=projection_object)) this_num_holes = len(buffer_vertex_dict[polygons.HOLE_X_COLUMN]) for k in range(this_num_holes): (buffer_vertex_dict[polygons.HOLE_Y_COLUMN][k], buffer_vertex_dict[polygons.HOLE_X_COLUMN][k]) = ( projections.project_xy_to_latlng( buffer_vertex_dict[polygons.HOLE_X_COLUMN][k], buffer_vertex_dict[polygons.HOLE_Y_COLUMN][k], projection_object=projection_object)) buffer_polygon_object_latlng = ( polygons.vertex_arrays_to_polygon_object( buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list= buffer_vertex_dict[polygons.HOLE_X_COLUMN], hole_y_coords_list= buffer_vertex_dict[polygons.HOLE_Y_COLUMN])) storm_object_table[buffer_column_names[j]].values[ i] = buffer_polygon_object_latlng return storm_object_table
def _plot_storm_outlines_one_time(storm_object_table, valid_time_unix_sec, warning_table, axes_object, basemap_object, storm_outline_colour, storm_outline_opacity, include_secondary_ids, output_dir_name, primary_id_to_track_colour=None, radar_matrix=None, radar_field_name=None, radar_latitudes_deg=None, radar_longitudes_deg=None, radar_colour_map_object=None): """Plots storm outlines (and may underlay radar data) at one time step. M = number of rows in radar grid N = number of columns in radar grid K = number of storm objects If `primary_id_to_track_colour is None`, all storm tracks will be the same colour. :param storm_object_table: See doc for `storm_plotting.plot_storm_outlines`. :param valid_time_unix_sec: Will plot storm outlines only at this time. Will plot tracks up to and including this time. :param warning_table: None or a pandas table with the following columns. warning_table.start_time_unix_sec: Start time. warning_table.end_time_unix_sec: End time. warning_table.polygon_object_latlng: Polygon (instance of `shapely.geometry.Polygon`) with lat-long coordinates of warning boundary. :param axes_object: See doc for `storm_plotting.plot_storm_outlines`. :param basemap_object: Same. :param storm_outline_colour: Same. :param storm_outline_opacity: Same. :param include_secondary_ids: Same. :param output_dir_name: See documentation at top of file. :param primary_id_to_track_colour: Dictionary created by `_assign_colours_to_storms`. If this is None, all storm tracks will be the same colour. :param radar_matrix: M-by-N numpy array of radar values. If `radar_matrix is None`, radar data will simply not be plotted. :param radar_field_name: [used only if `radar_matrix is not None`] See documentation at top of file. :param radar_latitudes_deg: [used only if `radar_matrix is not None`] length-M numpy array of grid-point latitudes (deg N). :param radar_longitudes_deg: [used only if `radar_matrix is not None`] length-N numpy array of grid-point longitudes (deg E). :param radar_colour_map_object: [used only if `radar_matrix is not None`] Colour map (instance of `matplotlib.pyplot.cm`). If None, will use default for the given field. """ # plot_storm_ids = radar_matrix is None or radar_colour_map_object is None plot_storm_ids = False min_plot_latitude_deg = basemap_object.llcrnrlat max_plot_latitude_deg = basemap_object.urcrnrlat min_plot_longitude_deg = basemap_object.llcrnrlon max_plot_longitude_deg = basemap_object.urcrnrlon plotting_utils.plot_coastlines(basemap_object=basemap_object, axes_object=axes_object, line_colour=BORDER_COLOUR) plotting_utils.plot_countries(basemap_object=basemap_object, axes_object=axes_object, line_colour=BORDER_COLOUR) plotting_utils.plot_states_and_provinces(basemap_object=basemap_object, axes_object=axes_object, line_colour=BORDER_COLOUR) plotting_utils.plot_parallels(basemap_object=basemap_object, axes_object=axes_object, num_parallels=NUM_PARALLELS) plotting_utils.plot_meridians(basemap_object=basemap_object, axes_object=axes_object, num_meridians=NUM_MERIDIANS) if radar_matrix is not None: custom_colour_map = radar_colour_map_object is not None good_indices = numpy.where( numpy.logical_and(radar_latitudes_deg >= min_plot_latitude_deg, radar_latitudes_deg <= max_plot_latitude_deg))[0] radar_latitudes_deg = radar_latitudes_deg[good_indices] radar_matrix = radar_matrix[good_indices, :] good_indices = numpy.where( numpy.logical_and( radar_longitudes_deg >= min_plot_longitude_deg, radar_longitudes_deg <= max_plot_longitude_deg))[0] radar_longitudes_deg = radar_longitudes_deg[good_indices] radar_matrix = radar_matrix[:, good_indices] latitude_spacing_deg = radar_latitudes_deg[1] - radar_latitudes_deg[0] longitude_spacing_deg = (radar_longitudes_deg[1] - radar_longitudes_deg[0]) if radar_colour_map_object is None: colour_map_object, colour_norm_object = ( radar_plotting.get_default_colour_scheme(radar_field_name)) else: colour_map_object = radar_colour_map_object colour_norm_object = radar_plotting.get_default_colour_scheme( radar_field_name)[-1] this_ratio = radar_plotting._field_to_plotting_units( field_matrix=1., field_name=radar_field_name) colour_norm_object = pyplot.Normalize( colour_norm_object.vmin / this_ratio, colour_norm_object.vmax / this_ratio) radar_plotting.plot_latlng_grid( field_matrix=radar_matrix, field_name=radar_field_name, axes_object=axes_object, min_grid_point_latitude_deg=numpy.min(radar_latitudes_deg), min_grid_point_longitude_deg=numpy.min(radar_longitudes_deg), latitude_spacing_deg=latitude_spacing_deg, longitude_spacing_deg=longitude_spacing_deg, colour_map_object=colour_map_object, colour_norm_object=colour_norm_object) latitude_range_deg = max_plot_latitude_deg - min_plot_latitude_deg longitude_range_deg = max_plot_longitude_deg - min_plot_longitude_deg if latitude_range_deg > longitude_range_deg: orientation_string = 'vertical' else: orientation_string = 'horizontal' colour_bar_object = plotting_utils.plot_colour_bar( axes_object_or_matrix=axes_object, data_matrix=radar_matrix, colour_map_object=colour_map_object, colour_norm_object=colour_norm_object, orientation_string=orientation_string, padding=0.05, extend_min=radar_field_name in radar_plotting.SHEAR_VORT_DIV_NAMES, extend_max=True, fraction_of_axis_length=1.) radar_field_name_verbose = radar_utils.field_name_to_verbose( field_name=radar_field_name, include_units=True) radar_field_name_verbose = radar_field_name_verbose.replace( 'm ASL', 'kft ASL') colour_bar_object.set_label(radar_field_name_verbose) if custom_colour_map: tick_values = colour_bar_object.get_ticks() tick_label_strings = ['{0:.1f}'.format(v) for v in tick_values] colour_bar_object.set_ticks(tick_values) colour_bar_object.set_ticklabels(tick_label_strings) valid_time_rows = numpy.where(storm_object_table[ tracking_utils.VALID_TIME_COLUMN].values == valid_time_unix_sec)[0] this_colour = matplotlib.colors.to_rgba(storm_outline_colour, storm_outline_opacity) storm_plotting.plot_storm_outlines( storm_object_table=storm_object_table.iloc[valid_time_rows], axes_object=axes_object, basemap_object=basemap_object, line_colour=this_colour) if plot_storm_ids: storm_plotting.plot_storm_ids( storm_object_table=storm_object_table.iloc[valid_time_rows], axes_object=axes_object, basemap_object=basemap_object, plot_near_centroids=False, include_secondary_ids=include_secondary_ids, font_colour=storm_plotting.DEFAULT_FONT_COLOUR) if warning_table is not None: warning_indices = numpy.where( numpy.logical_and( warning_table[WARNING_START_TIME_KEY].values <= valid_time_unix_sec, warning_table[WARNING_END_TIME_KEY].values >= valid_time_unix_sec))[0] for k in warning_indices: this_vertex_dict = polygons.polygon_object_to_vertex_arrays( warning_table[WARNING_LATLNG_POLYGON_KEY].values[k]) these_latitudes_deg = this_vertex_dict[polygons.EXTERIOR_Y_COLUMN] these_longitudes_deg = this_vertex_dict[polygons.EXTERIOR_X_COLUMN] these_latitude_flags = numpy.logical_and( these_latitudes_deg >= min_plot_latitude_deg, these_latitudes_deg <= max_plot_latitude_deg) these_longitude_flags = numpy.logical_and( these_longitudes_deg >= min_plot_longitude_deg, these_longitudes_deg <= max_plot_longitude_deg) these_coord_flags = numpy.logical_and(these_latitude_flags, these_longitude_flags) if not numpy.any(these_coord_flags): continue these_x_metres, these_y_metres = basemap_object( these_longitudes_deg, these_latitudes_deg) axes_object.plot(these_x_metres, these_y_metres, color=this_colour, linestyle='dashed', linewidth=storm_plotting.DEFAULT_POLYGON_WIDTH) axes_object.text(numpy.mean(these_x_metres), numpy.mean(these_y_metres), 'W{0:d}'.format(k), fontsize=storm_plotting.DEFAULT_FONT_SIZE, fontweight='bold', color=this_colour, horizontalalignment='center', verticalalignment='center') these_sec_id_strings = ( warning_table[LINKED_SECONDARY_IDS_KEY].values[k]) if len(these_sec_id_strings) == 0: continue these_object_indices = numpy.array([], dtype=int) for this_sec_id_string in these_sec_id_strings: these_subindices = numpy.where( storm_object_table[tracking_utils.SECONDARY_ID_COLUMN]. values[valid_time_rows] == this_sec_id_string)[0] these_object_indices = numpy.concatenate( (these_object_indices, valid_time_rows[these_subindices])) for i in these_object_indices: this_vertex_dict = polygons.polygon_object_to_vertex_arrays( storm_object_table[ tracking_utils.LATLNG_POLYGON_COLUMN].values[i]) these_x_metres, these_y_metres = basemap_object( this_vertex_dict[polygons.EXTERIOR_X_COLUMN], this_vertex_dict[polygons.EXTERIOR_Y_COLUMN]) axes_object.text(numpy.mean(these_x_metres), numpy.mean(these_y_metres), 'W{0:d}'.format(k), fontsize=storm_plotting.DEFAULT_FONT_SIZE, fontweight='bold', color=this_colour, horizontalalignment='center', verticalalignment='center') if primary_id_to_track_colour is None: storm_plotting.plot_storm_tracks(storm_object_table=storm_object_table, axes_object=axes_object, basemap_object=basemap_object, colour_map_object=None, constant_colour=DEFAULT_TRACK_COLOUR) else: for this_primary_id_string in primary_id_to_track_colour: this_storm_object_table = storm_object_table.loc[ storm_object_table[tracking_utils.PRIMARY_ID_COLUMN] == this_primary_id_string] if len(this_storm_object_table.index) == 0: continue storm_plotting.plot_storm_tracks( storm_object_table=this_storm_object_table, axes_object=axes_object, basemap_object=basemap_object, colour_map_object=None, constant_colour=primary_id_to_track_colour[ this_primary_id_string]) nice_time_string = time_conversion.unix_sec_to_string( valid_time_unix_sec, NICE_TIME_FORMAT) abbrev_time_string = time_conversion.unix_sec_to_string( valid_time_unix_sec, FILE_NAME_TIME_FORMAT) pyplot.title('Storm objects at {0:s}'.format(nice_time_string)) output_file_name = '{0:s}/storm_outlines_{1:s}.jpg'.format( output_dir_name, abbrev_time_string) print('Saving figure to: "{0:s}"...'.format(output_file_name)) pyplot.savefig(output_file_name, dpi=FIGURE_RESOLUTION_DPI, pad_inches=0, bbox_inches='tight') pyplot.close()
def plot_one_storm_cell_to_winds( storm_to_winds_table, storm_id, basemap_object=None, axes_object=None, storm_colour=storm_plotting.DEFAULT_TRACK_COLOUR, storm_line_width=storm_plotting.DEFAULT_TRACK_WIDTH, wind_barb_length=wind_plotting.DEFAULT_BARB_LENGTH, empty_wind_barb_radius=wind_plotting.DEFAULT_EMPTY_BARB_RADIUS, fill_empty_wind_barb=wind_plotting.FILL_EMPTY_BARB_DEFAULT, wind_colour_map=wind_plotting.DEFAULT_COLOUR_MAP, colour_minimum_kt=wind_plotting.DEFAULT_COLOUR_MINIMUM_KT, colour_maximum_kt=wind_plotting.DEFAULT_COLOUR_MAXIMUM_KT): """Plots wind observations linked to one storm cell. :param storm_to_winds_table: pandas DataFrame with columns documented in `link_storms_to_winds.write_storm_to_winds_table`. :param storm_id: String ID for storm cell. Only this storm cell and wind observations linked thereto will be plotted. :param basemap_object: Instance of `mpl_toolkits.basemap.Basemap`. :param axes_object: Instance of `matplotlib.axes._subplots.AxesSubplot`. :param storm_colour: Colour for storm track, first storm object, and last storm object (in any format accepted by `matplotlib.colors`). :param storm_line_width: Line width for storm track, first storm object, and last storm object (real positive number). :param wind_barb_length: Length of each wind barb. :param empty_wind_barb_radius: Radius of circle for 0-metre-per-second wind barb. :param fill_empty_wind_barb: Boolean flag. If fill_empty_barb = True, 0-metre-per-second wind barb will be a filled circle. Otherwise, it will be an empty circle. :param wind_colour_map: Instance of `matplotlib.pyplot.cm`. :param colour_minimum_kt: Minimum speed for colour map (kt or nautical miles per hour). :param colour_maximum_kt: Maximum speed for colour map (kt or nautical miles per hour). """ error_checking.assert_is_string(storm_id) error_checking.assert_is_geq(colour_minimum_kt, 0.) error_checking.assert_is_greater(colour_maximum_kt, colour_minimum_kt) storm_cell_flags = [this_id == storm_id for this_id in storm_to_winds_table[ tracking_io.STORM_ID_COLUMN].values] storm_cell_rows = numpy.where(numpy.array(storm_cell_flags))[0] centroid_latitudes_deg = storm_to_winds_table[ tracking_io.CENTROID_LAT_COLUMN].values[storm_cell_rows] centroid_longitudes_deg = storm_to_winds_table[ tracking_io.CENTROID_LNG_COLUMN].values[storm_cell_rows] storm_plotting.plot_storm_track( basemap_object=basemap_object, axes_object=axes_object, latitudes_deg=centroid_latitudes_deg, longitudes_deg=centroid_longitudes_deg, line_colour=storm_colour, line_width=storm_line_width) storm_times_unix_sec = storm_to_winds_table[tracking_io.TIME_COLUMN].values[ storm_cell_rows] first_storm_object_row = storm_cell_rows[numpy.argmin(storm_times_unix_sec)] last_storm_object_row = storm_cell_rows[numpy.argmax(storm_times_unix_sec)] first_vertex_dict = polygons.polygon_object_to_vertex_arrays( storm_to_winds_table[tracking_io.POLYGON_OBJECT_LATLNG_COLUMN].values[ first_storm_object_row]) first_vertex_latitudes_deg = first_vertex_dict[polygons.EXTERIOR_Y_COLUMN] first_vertex_longitudes_deg = first_vertex_dict[polygons.EXTERIOR_X_COLUMN] storm_plotting.plot_unfilled_polygon( basemap_object=basemap_object, axes_object=axes_object, vertex_latitudes_deg=first_vertex_latitudes_deg, vertex_longitudes_deg=first_vertex_longitudes_deg, exterior_colour=storm_colour, exterior_line_width=storm_line_width) last_vertex_dict = polygons.polygon_object_to_vertex_arrays( storm_to_winds_table[tracking_io.POLYGON_OBJECT_LATLNG_COLUMN].values[ last_storm_object_row]) last_vertex_latitudes_deg = last_vertex_dict[polygons.EXTERIOR_Y_COLUMN] last_vertex_longitudes_deg = last_vertex_dict[polygons.EXTERIOR_X_COLUMN] storm_plotting.plot_unfilled_polygon( basemap_object=basemap_object, axes_object=axes_object, vertex_latitudes_deg=last_vertex_latitudes_deg, vertex_longitudes_deg=last_vertex_longitudes_deg, exterior_colour=storm_colour, exterior_line_width=storm_line_width) wind_latitudes_deg = numpy.array([]) wind_longitudes_deg = numpy.array([]) u_winds_m_s01 = numpy.array([]) v_winds_m_s01 = numpy.array([]) for this_row in storm_cell_rows: wind_latitudes_deg = numpy.concatenate(( wind_latitudes_deg, storm_to_winds_table[ storms_to_winds.WIND_LATITUDES_COLUMN].values[this_row])) wind_longitudes_deg = numpy.concatenate(( wind_longitudes_deg, storm_to_winds_table[ storms_to_winds.WIND_LONGITUDES_COLUMN].values[this_row])) u_winds_m_s01 = numpy.concatenate(( u_winds_m_s01, storm_to_winds_table[ storms_to_winds.U_WINDS_COLUMN].values[this_row])) v_winds_m_s01 = numpy.concatenate(( v_winds_m_s01, storm_to_winds_table[ storms_to_winds.V_WINDS_COLUMN].values[this_row])) wind_plotting.plot_wind_barbs( basemap_object=basemap_object, axes_object=axes_object, latitudes_deg=wind_latitudes_deg, longitudes_deg=wind_longitudes_deg, u_winds_m_s01=u_winds_m_s01, v_winds_m_s01=v_winds_m_s01, barb_length=wind_barb_length, empty_barb_radius=empty_wind_barb_radius, fill_empty_barb=fill_empty_wind_barb, colour_map=wind_colour_map, colour_minimum_kt=colour_minimum_kt, colour_maximum_kt=colour_maximum_kt)
def _run(input_outlook_file_name, input_warning_file_name, border_colour, warning_colour, min_plot_latitude_deg, max_plot_latitude_deg, min_plot_longitude_deg, max_plot_longitude_deg, output_file_name): """Plots SPC convective outlook. This is effectively the main method. :param input_outlook_file_name: See documentation at top of file. :param input_warning_file_name: Same. :param border_colour: Same. :param warning_colour: Same. :param min_plot_latitude_deg: Same. :param max_plot_latitude_deg: Same. :param min_plot_longitude_deg: Same. :param max_plot_longitude_deg: Same. :param output_file_name: Same. """ print( 'Reading SPC outlook from: "{0:s}"...'.format(input_outlook_file_name)) pickle_file_handle = open(input_outlook_file_name, 'rb') outlook_table = pickle.load(pickle_file_handle) pickle_file_handle.close() risk_type_enums = numpy.array([ RISK_TYPE_STRING_TO_ENUM[s] for s in outlook_table[RISK_TYPE_COLUMN].values ], dtype=int) sort_indices = numpy.argsort(risk_type_enums) outlook_table = outlook_table.iloc[sort_indices] outlook_table = _get_bounding_boxes(outlook_table) if input_warning_file_name in ['', 'None']: warning_table = None else: print('Reading tornado warnings from: "{0:s}"...'.format( input_warning_file_name)) pickle_file_handle = open(input_warning_file_name, 'rb') warning_table = pickle.load(pickle_file_handle) pickle_file_handle.close() warning_table = _get_bounding_boxes(warning_table) latitude_limits_deg, longitude_limits_deg = _get_plotting_limits( min_plot_latitude_deg=min_plot_latitude_deg, max_plot_latitude_deg=max_plot_latitude_deg, min_plot_longitude_deg=min_plot_longitude_deg, max_plot_longitude_deg=max_plot_longitude_deg, outlook_table=outlook_table, warning_table=warning_table) min_plot_latitude_deg = latitude_limits_deg[0] max_plot_latitude_deg = latitude_limits_deg[1] min_plot_longitude_deg = longitude_limits_deg[0] max_plot_longitude_deg = longitude_limits_deg[1] print(('Plotting limits = [{0:.2f}, {1:.2f}] deg N and [{2:.2f}, {3:.2f}] ' 'deg E').format(min_plot_latitude_deg, max_plot_latitude_deg, min_plot_longitude_deg, max_plot_longitude_deg)) latlng_limit_dict = { plotting_utils.MIN_LATITUDE_KEY: min_plot_latitude_deg, plotting_utils.MAX_LATITUDE_KEY: max_plot_latitude_deg, plotting_utils.MIN_LONGITUDE_KEY: min_plot_longitude_deg, plotting_utils.MAX_LONGITUDE_KEY: max_plot_longitude_deg } axes_object, basemap_object = plotting_utils.create_map_with_nwp_proj( model_name=nwp_model_utils.RAP_MODEL_NAME, grid_name=nwp_model_utils.NAME_OF_130GRID, xy_limit_dict=None, latlng_limit_dict=latlng_limit_dict, resolution_string='i')[1:] plotting_utils.plot_coastlines(basemap_object=basemap_object, axes_object=axes_object, line_colour=border_colour) plotting_utils.plot_countries(basemap_object=basemap_object, axes_object=axes_object, line_colour=border_colour) plotting_utils.plot_states_and_provinces(basemap_object=basemap_object, axes_object=axes_object, line_colour=border_colour) _, unique_risk_type_indices = numpy.unique( outlook_table[RISK_TYPE_COLUMN].values, return_index=True) num_outlooks = len(outlook_table.index) legend_handles = [] legend_strings = [] for i in range(num_outlooks): this_risk_type_string = outlook_table[RISK_TYPE_COLUMN].values[i] this_colour = RISK_TYPE_STRING_TO_COLOUR[this_risk_type_string] this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( outlook_table[POLYGON_COLUMN].values[i]) these_x_coords_metres, these_y_coords_metres = basemap_object( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) this_polygon_object_xy = polygons.vertex_arrays_to_polygon_object( exterior_x_coords=these_x_coords_metres, exterior_y_coords=these_y_coords_metres) this_patch_object = PolygonPatch(this_polygon_object_xy, lw=0., ec=this_colour, fc=this_colour, alpha=OUTLOOK_OPACITY) this_handle = axes_object.add_patch(this_patch_object) if i in unique_risk_type_indices: this_string = '{0:s}{1:s}'.format(this_risk_type_string[0].upper(), this_risk_type_string[1:]) legend_strings.append(this_string) legend_handles.append(this_handle) if warning_table is None: num_warnings = 0 else: num_warnings = len(warning_table.index) warning_colour_tuple = plotting_utils.colour_from_numpy_to_tuple( warning_colour) for i in range(num_warnings): this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( warning_table[POLYGON_COLUMN].values[i]) these_x_coords_metres, these_y_coords_metres = basemap_object( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) axes_object.plot(these_x_coords_metres, these_y_coords_metres, color=warning_colour_tuple, linestyle='solid', linewidth=WARNING_LINE_WIDTH) axes_object.legend(legend_handles, legend_strings, loc='upper left') print('Saving figure to: "{0:s}"...'.format(output_file_name)) file_system_utils.mkdir_recursive_if_necessary(file_name=output_file_name) pyplot.savefig(output_file_name, dpi=FIGURE_RESOLUTION_DPI) pyplot.close() imagemagick_utils.trim_whitespace(input_file_name=output_file_name, output_file_name=output_file_name)
def create_distance_buffers(storm_object_table, min_distances_metres, max_distances_metres): """Creates one or more distance buffers around each storm object. K = number of buffers :param storm_object_table: pandas DataFrame with the following columns. Each row is one storm object. storm_object_table.centroid_latitude_deg: Latitude (deg N) of storm-object centroid. storm_object_table.centroid_longitude_deg: Longitude (deg E) of storm-object centroid. storm_object_table.polygon_object_latlng_deg: Instance of `shapely.geometry.Polygon`, with x-coords in longitude (deg E) and y-coords in latitude (deg N). :param min_distances_metres: length-K numpy array of minimum distances. If the storm object is inside the [k]th buffer -- i.e., the [k]th buffer has no minimum distance -- then min_distances_metres[k] should be NaN. :param max_distances_metres: length-K numpy array of max distances. :return: storm_object_table: Same as input but with K additional columns (one per distance buffer). Column names are generated by `buffer_to_column_name`, and each value in these columns is a `shapely.geometry.Polygon` object, with x-coords in longitude (deg E) and y-coords in latitude (deg N). """ num_buffers = len(min_distances_metres) these_expected_dim = numpy.array([num_buffers], dtype=int) error_checking.assert_is_numpy_array(max_distances_metres, exact_dimensions=these_expected_dim) global_centroid_lat_deg, global_centroid_lng_deg = ( geodetic_utils.get_latlng_centroid( latitudes_deg=storm_object_table[CENTROID_LATITUDE_COLUMN].values, longitudes_deg=storm_object_table[CENTROID_LONGITUDE_COLUMN].values )) projection_object = projections.init_azimuthal_equidistant_projection( central_latitude_deg=global_centroid_lat_deg, central_longitude_deg=global_centroid_lng_deg) num_storm_objects = len(storm_object_table.index) object_array = numpy.full(num_storm_objects, numpy.nan, dtype=object) buffer_column_names = [''] * num_buffers for j in range(num_buffers): buffer_column_names[j] = buffer_to_column_name( min_distance_metres=min_distances_metres[j], max_distance_metres=max_distances_metres[j]) storm_object_table = storm_object_table.assign( **{buffer_column_names[j]: object_array}) for i in range(num_storm_objects): this_orig_vertex_dict_latlng_deg = ( polygons.polygon_object_to_vertex_arrays( storm_object_table[LATLNG_POLYGON_COLUMN].values[i])) these_orig_x_metres, these_orig_y_metres = ( projections.project_latlng_to_xy( latitudes_deg=this_orig_vertex_dict_latlng_deg[ polygons.EXTERIOR_Y_COLUMN], longitudes_deg=this_orig_vertex_dict_latlng_deg[ polygons.EXTERIOR_X_COLUMN], projection_object=projection_object)) for j in range(num_buffers): this_buffer_poly_object_xy_metres = polygons.buffer_simple_polygon( vertex_x_metres=these_orig_x_metres, vertex_y_metres=these_orig_y_metres, min_buffer_dist_metres=min_distances_metres[j], max_buffer_dist_metres=max_distances_metres[j]) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_poly_object_xy_metres) (this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN] ) = projections.project_xy_to_latlng( x_coords_metres=this_buffer_vertex_dict[ polygons.EXTERIOR_X_COLUMN], y_coords_metres=this_buffer_vertex_dict[ polygons.EXTERIOR_Y_COLUMN], projection_object=projection_object) this_num_holes = len( this_buffer_vertex_dict[polygons.HOLE_X_COLUMN]) for k in range(this_num_holes): (this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN][k], this_buffer_vertex_dict[polygons.HOLE_X_COLUMN][k] ) = projections.project_xy_to_latlng( x_coords_metres=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN][k], y_coords_metres=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN][k], projection_object=projection_object) this_buffer_poly_object_latlng_deg = ( polygons.vertex_arrays_to_polygon_object( exterior_x_coords=this_buffer_vertex_dict[ polygons.EXTERIOR_X_COLUMN], exterior_y_coords=this_buffer_vertex_dict[ polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) storm_object_table[buffer_column_names[j]].values[i] = ( this_buffer_poly_object_latlng_deg) return storm_object_table
MIN_OBSERVATIONS_FOR_SAMPLING = 25 NUM_DEAD_STORM_OBJECTS_TO_SELECT = 3 LIVE_SELECTED_INDICES_FOR_MIN_OBS_SAMPLING = numpy.array( [5, 6, 9, 10, 12, 13, 14, 15, 18, 20, 21]) LIVE_SELECTED_INDICES_FOR_MIN_OBS_PLUS_SAMPLING = numpy.array( [5, 6, 9, 10, 12, 13, 14, 15, 18, 20, 21, 23, 24]) LIVE_SELECTED_INDICES_FOR_MIN_DENSITY_SAMPLING = copy.deepcopy( LIVE_SELECTED_INDICES_FOR_MIN_OBS_SAMPLING) LIVE_SELECTED_INDICES_FOR_MIN_DENSITY_PLUS_SAMPLING = copy.deepcopy( LIVE_SELECTED_INDICES_FOR_MIN_OBS_PLUS_SAMPLING) # The following constants are used to test _get_observation_densities. POLYGON_OBJECT_XY, THIS_PROJECTION_OBJECT = polygons.project_latlng_to_xy( POLYGON_OBJECT_LATLNG) VERTEX_DICT_XY = polygons.polygon_object_to_vertex_arrays(POLYGON_OBJECT_XY) BUFFERED_POLYGON_OBJECT_XY = polygons.buffer_simple_polygon( VERTEX_DICT_XY[polygons.EXTERIOR_X_COLUMN], VERTEX_DICT_XY[polygons.EXTERIOR_Y_COLUMN], min_buffer_dist_metres=MIN_DISTANCE_METRES, max_buffer_dist_metres=MAX_DISTANCE_METRES) BUFFER_AREA_METRES2 = BUFFERED_POLYGON_OBJECT_XY.area OBSERVATION_DENSITY_BY_STORM_OBJECT_M02 = (NUM_OBSERVATIONS_BY_STORM_OBJECT / BUFFER_AREA_METRES2) MIN_OBS_DENSITY_FOR_SAMPLING_M02 = (MIN_OBSERVATIONS_FOR_SAMPLING / BUFFER_AREA_METRES2) BUFFERED_POLYGON_OBJECT_LATLNG = polygons.project_xy_to_latlng( BUFFERED_POLYGON_OBJECT_XY, THIS_PROJECTION_OBJECT) BUFFERED_POLYGON_OBJECT_ARRAY_LATLNG = numpy.full(
def make_buffers_around_polygons(storm_object_table, min_buffer_dists_metres=None, max_buffer_dists_metres=None): """Creates one or more buffers around each storm polygon. N = number of buffers V = number of vertices in a given polygon :param storm_object_table: pandas DataFrame with the following columns. storm_object_table.storm_id: String ID for storm cell. storm_object_table.polygon_object_latlng: Instance of `shapely.geometry.Polygon`, with vertices in lat-long coordinates. :param min_buffer_dists_metres: length-N numpy array of minimum buffer distances. If min_buffer_dists_metres[i] is NaN, the [i]th buffer includes the original polygon. If min_buffer_dists_metres[i] is defined, the [i]th buffer is a "nested" buffer, not including the original polygon. :param max_buffer_dists_metres: length-N numpy array of maximum buffer distances. Must be all real numbers (no NaN). :return: storm_object_table: Same as input, but with N extra columns. storm_object_table.polygon_object_latlng_buffer_<D>m: Instance of `shapely.geometry.Polygon` for D-metre buffer around storm. storm_object_table.polygon_object_latlng_buffer_<d>_<D>m: Instance of `shapely.geometry.Polygon` for d-to-D-metre buffer around storm. """ error_checking.assert_is_geq_numpy_array(min_buffer_dists_metres, 0., allow_nan=True) error_checking.assert_is_numpy_array(min_buffer_dists_metres, num_dimensions=1) num_buffers = len(min_buffer_dists_metres) error_checking.assert_is_geq_numpy_array(max_buffer_dists_metres, 0., allow_nan=False) error_checking.assert_is_numpy_array(max_buffer_dists_metres, exact_dimensions=numpy.array( [num_buffers])) for j in range(num_buffers): if numpy.isnan(min_buffer_dists_metres[j]): continue error_checking.assert_is_greater(max_buffer_dists_metres[j], min_buffer_dists_metres[j], allow_nan=False) num_storm_objects = len(storm_object_table.index) centroid_latitudes_deg = numpy.full(num_storm_objects, numpy.nan) centroid_longitudes_deg = numpy.full(num_storm_objects, numpy.nan) for i in range(num_storm_objects): this_centroid_object = storm_object_table[ POLYGON_OBJECT_LATLNG_COLUMN].values[0].centroid centroid_latitudes_deg[i] = this_centroid_object.y centroid_longitudes_deg[i] = this_centroid_object.x global_centroid_lat_deg, global_centroid_lng_deg = ( polygons.get_latlng_centroid(centroid_latitudes_deg, centroid_longitudes_deg)) projection_object = projections.init_azimuthal_equidistant_projection( global_centroid_lat_deg, global_centroid_lng_deg) object_array = numpy.full(num_storm_objects, numpy.nan, dtype=object) argument_dict = {} buffer_column_names = [''] * num_buffers for j in range(num_buffers): buffer_column_names[j] = distance_buffer_to_column_name( min_buffer_dists_metres[j], max_buffer_dists_metres[j]) argument_dict.update({buffer_column_names[j]: object_array}) storm_object_table = storm_object_table.assign(**argument_dict) for i in range(num_storm_objects): orig_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( storm_object_table[POLYGON_OBJECT_LATLNG_COLUMN].values[i]) (orig_vertex_x_metres, orig_vertex_y_metres) = projections.project_latlng_to_xy( orig_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN], orig_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], projection_object=projection_object) for j in range(num_buffers): buffer_polygon_object_xy = polygons.buffer_simple_polygon( orig_vertex_x_metres, orig_vertex_y_metres, min_buffer_dist_metres=min_buffer_dists_metres[j], max_buffer_dist_metres=max_buffer_dists_metres[j]) buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( buffer_polygon_object_xy) (buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN]) = ( projections.project_xy_to_latlng( buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], projection_object=projection_object)) this_num_holes = len(buffer_vertex_dict[polygons.HOLE_X_COLUMN]) for k in range(this_num_holes): (buffer_vertex_dict[polygons.HOLE_Y_COLUMN][k], buffer_vertex_dict[polygons.HOLE_X_COLUMN][k]) = ( projections.project_xy_to_latlng( buffer_vertex_dict[polygons.HOLE_X_COLUMN][k], buffer_vertex_dict[polygons.HOLE_Y_COLUMN][k], projection_object=projection_object)) buffer_polygon_object_latlng = ( polygons.vertex_arrays_to_polygon_object( buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) storm_object_table[buffer_column_names[j]].values[ i] = buffer_polygon_object_latlng return storm_object_table
def plot_storm_ids( storm_object_table, axes_object, basemap_object, plot_near_centroids=False, include_secondary_ids=False, font_colour=DEFAULT_FONT_COLOUR, font_size=DEFAULT_FONT_SIZE): """Plots storm IDs as text. :param storm_object_table: See doc for `plot_storm_outlines`. :param axes_object: Same. :param basemap_object: Same. :param plot_near_centroids: Boolean flag. If True, will plot each ID near the storm centroid. If False, will plot each ID near southeasternmost point in storm outline. :param include_secondary_ids: Boolean flag. If True, will plot full IDs (primary_secondary). If False, will plot only primary IDs. :param font_colour: Font colour. :param font_size: Font size. """ error_checking.assert_is_boolean(plot_near_centroids) error_checking.assert_is_boolean(include_secondary_ids) font_colour_tuple = plotting_utils.colour_from_numpy_to_tuple(font_colour) num_storm_objects = len(storm_object_table.index) if plot_near_centroids: text_x_coords_metres, text_y_coords_metres = basemap_object( storm_object_table[tracking_utils.CENTROID_LONGITUDE_COLUMN].values, storm_object_table[tracking_utils.CENTROID_LATITUDE_COLUMN].values ) else: text_x_coords_metres = numpy.full(num_storm_objects, numpy.nan) text_y_coords_metres = numpy.full(num_storm_objects, numpy.nan) for i in range(num_storm_objects): this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( storm_object_table[ tracking_utils.LATLNG_POLYGON_COLUMN].values[i] ) these_x_metres, these_y_metres = basemap_object( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN] ) this_index = numpy.argmax(these_x_metres - these_y_metres) text_x_coords_metres[i] = these_x_metres[this_index] text_y_coords_metres[i] = these_y_metres[this_index] for i in range(num_storm_objects): this_primary_id_string = storm_object_table[ tracking_utils.PRIMARY_ID_COLUMN].values[i] try: this_primary_id_string = this_primary_id_string[-4:] except ValueError: pass if include_secondary_ids: this_secondary_id_string = storm_object_table[ tracking_utils.SECONDARY_ID_COLUMN].values[i] try: this_secondary_id_string = this_secondary_id_string[-4:] except ValueError: pass this_label_string = '{0:s}_{1:s}'.format( this_primary_id_string, this_secondary_id_string) else: this_label_string = this_primary_id_string axes_object.text( text_x_coords_metres[i], text_y_coords_metres[i], this_label_string, fontsize=font_size, fontweight='bold', color=font_colour_tuple, horizontalalignment='left', verticalalignment='top')
def _run(input_file_name, border_colour, polygon_colour, min_plot_latitude_deg, max_plot_latitude_deg, min_plot_longitude_deg, max_plot_longitude_deg, output_file_name): """Plots tornado-warning polygons. This is effectively the main method. :param input_file_name: See documentation at top of file. :param border_colour: Same. :param polygon_colour: Same. :param min_plot_latitude_deg: Same. :param max_plot_latitude_deg: Same. :param min_plot_longitude_deg: Same. :param max_plot_longitude_deg: Same. :param output_file_name: Same. """ print('Reading data from: "{0:s}"...'.format(input_file_name)) pickle_file_handle = open(input_file_name, 'rb') warning_table = pickle.load(pickle_file_handle) pickle_file_handle.close() num_warnings = len(warning_table.index) warning_min_latitudes_deg = numpy.full(num_warnings, numpy.nan) warning_max_latitudes_deg = numpy.full(num_warnings, numpy.nan) warning_min_longitudes_deg = numpy.full(num_warnings, numpy.nan) warning_max_longitudes_deg = numpy.full(num_warnings, numpy.nan) for i in range(num_warnings): this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( warning_table[POLYGON_COLUMN].values[i]) warning_min_latitudes_deg[i] = numpy.min( this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) warning_max_latitudes_deg[i] = numpy.max( this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) warning_min_longitudes_deg[i] = numpy.min( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN]) warning_max_longitudes_deg[i] = numpy.max( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN]) if min_plot_latitude_deg <= SENTINEL_VALUE: min_plot_latitude_deg = (numpy.min(warning_min_latitudes_deg) - LATLNG_BUFFER_DEG) if max_plot_latitude_deg <= SENTINEL_VALUE: max_plot_latitude_deg = (numpy.max(warning_min_latitudes_deg) + LATLNG_BUFFER_DEG) if min_plot_longitude_deg <= SENTINEL_VALUE: min_plot_longitude_deg = (numpy.min(warning_min_longitudes_deg) - LATLNG_BUFFER_DEG) if max_plot_longitude_deg <= SENTINEL_VALUE: max_plot_longitude_deg = (numpy.max(warning_min_longitudes_deg) + LATLNG_BUFFER_DEG) good_latitude_flags = numpy.logical_and( warning_max_latitudes_deg >= min_plot_latitude_deg, warning_min_latitudes_deg <= max_plot_latitude_deg) good_longitude_flags = numpy.logical_and( warning_max_longitudes_deg >= min_plot_longitude_deg, warning_min_longitudes_deg <= max_plot_longitude_deg) good_indices = numpy.where( numpy.logical_and(good_latitude_flags, good_longitude_flags))[0] warning_table = warning_table.iloc[good_indices] _, axes_object, basemap_object = ( plotting_utils.create_equidist_cylindrical_map( min_latitude_deg=min_plot_latitude_deg, max_latitude_deg=max_plot_latitude_deg, min_longitude_deg=min_plot_longitude_deg, max_longitude_deg=max_plot_longitude_deg, resolution_string='i')) plotting_utils.plot_coastlines(basemap_object=basemap_object, axes_object=axes_object, line_colour=border_colour) plotting_utils.plot_countries(basemap_object=basemap_object, axes_object=axes_object, line_colour=border_colour) plotting_utils.plot_states_and_provinces(basemap_object=basemap_object, axes_object=axes_object, line_colour=border_colour) plotting_utils.plot_parallels(basemap_object=basemap_object, axes_object=axes_object, num_parallels=NUM_PARALLELS) plotting_utils.plot_meridians(basemap_object=basemap_object, axes_object=axes_object, num_meridians=NUM_MERIDIANS) num_warnings = len(warning_table.index) polygon_colour_tuple = plotting_utils.colour_from_numpy_to_tuple( polygon_colour) for i in range(num_warnings): this_vertex_dict_latlng = polygons.polygon_object_to_vertex_arrays( warning_table[POLYGON_COLUMN].values[i]) these_x_coords_metres, these_y_coords_metres = basemap_object( this_vertex_dict_latlng[polygons.EXTERIOR_X_COLUMN], this_vertex_dict_latlng[polygons.EXTERIOR_Y_COLUMN]) axes_object.plot(these_x_coords_metres, these_y_coords_metres, color=polygon_colour_tuple, linestyle='solid', linewidth=LINE_WIDTH) print('Saving figure to: "{0:s}"...'.format(output_file_name)) file_system_utils.mkdir_recursive_if_necessary(file_name=output_file_name) pyplot.savefig(output_file_name, dpi=FIGURE_RESOLUTION_DPI) pyplot.close() imagemagick_utils.trim_whitespace(input_file_name=output_file_name, output_file_name=output_file_name)