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))
Exemple #3
0
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)]
Exemple #8
0
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)
Exemple #9
0
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)
Exemple #10
0
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)
Exemple #17
0
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(
Exemple #20
0
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
Exemple #21
0
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)