def project_latlng_to_xy(polygon_object_latlng,
                         projection_object=None,
                         false_easting_metres=0,
                         false_northing_metres=0.):
    """Converts polygon from lat-long to x-y coordinates.

    :param polygon_object_latlng: `shapely.geometry.Polygon` object with
        vertices in lat-long coordinates.
    :param projection_object: `pyproj.Proj` object.  If None, this method will
        create an azimuthal equidistant projection centered at the polygon
        centroid.
    :param false_easting_metres: False easting (will be added to all x-
        coordinates).
    :param false_northing_metres: False northing (will be added to all y-
        coordinates).
    :return: polygon_object_xy_metres: `shapely.geometry.Polygon` object with
        vertices in x-y coordinates.
    :return: projection_object: `pyproj.Proj` object.  If input was defined,
        this is simply the input object.  If input was None, this is the object
        created on the fly.
    """

    if projection_object is None:
        centroid_object_latlng = polygon_object_latlng.centroid
        projection_object = projections.init_azimuthal_equidistant_projection(
            centroid_object_latlng.y, centroid_object_latlng.x)
        false_easting_metres = 0.
        false_northing_metres = 0.

    vertex_dict = polygon_object_to_vertex_arrays(polygon_object_latlng)
    vertex_dict[EXTERIOR_X_COLUMN], vertex_dict[EXTERIOR_Y_COLUMN] = (
        projections.project_latlng_to_xy(
            vertex_dict[EXTERIOR_Y_COLUMN],
            vertex_dict[EXTERIOR_X_COLUMN],
            projection_object=projection_object,
            false_easting_metres=false_easting_metres,
            false_northing_metres=false_northing_metres))

    num_holes = len(vertex_dict[HOLE_X_COLUMN])
    for i in range(num_holes):
        vertex_dict[HOLE_X_COLUMN][i], vertex_dict[HOLE_Y_COLUMN][i] = (
            projections.project_latlng_to_xy(
                vertex_dict[HOLE_Y_COLUMN][i],
                vertex_dict[HOLE_X_COLUMN][i],
                projection_object=projection_object,
                false_easting_metres=false_easting_metres,
                false_northing_metres=false_northing_metres))

    if num_holes == 0:
        polygon_object_xy = vertex_arrays_to_polygon_object(
            vertex_dict[EXTERIOR_X_COLUMN], vertex_dict[EXTERIOR_Y_COLUMN])
    else:
        polygon_object_xy = vertex_arrays_to_polygon_object(
            vertex_dict[EXTERIOR_X_COLUMN],
            vertex_dict[EXTERIOR_Y_COLUMN],
            hole_x_coords_list=vertex_dict[HOLE_X_COLUMN],
            hole_y_coords_list=vertex_dict[HOLE_Y_COLUMN])

    return polygon_object_xy, projection_object
def _init_azimuthal_equidistant_projection(storm_centroid_latitudes_deg,
                                           storm_centroid_longitudes_deg):
    """Initializes azimuthal equidistant projection.

    This projection will be centered at the "global centroid" (centroid of
    centroids) of all storm objects.

    N = number of storm objects

    :param storm_centroid_latitudes_deg: length-N numpy array with latitudes
        (deg N) of storm centroids.
    :param storm_centroid_longitudes_deg: length-N numpy array with longitudes
        (deg E) of storm centroids.
    :return: projection_object: Instance of `pyproj.Proj`, which can be used to
        convert between lat-long and x-y coordinates.
    """

    (global_centroid_lat_deg,
     global_centroid_lng_deg) = polygons.get_latlng_centroid(
         storm_centroid_latitudes_deg, storm_centroid_longitudes_deg)

    return projections.init_azimuthal_equidistant_projection(
        global_centroid_lat_deg, global_centroid_lng_deg)
import os
import copy
import pickle
import tempfile
import numpy
import pandas
from gewittergefahr.gg_io import storm_tracking_io as tracking_io
from gewittergefahr.gg_utils import best_tracks
from gewittergefahr.gg_utils import projections
from gewittergefahr.gg_utils import time_conversion
from gewittergefahr.gg_utils import file_system_utils
from gewittergefahr.gg_utils import error_checking

DAYS_TO_SECONDS = 86400
TIME_FORMAT_FOR_MESSAGES = '%Y-%m-%d-%H%M%S'
PROJECTION_OBJECT = projections.init_azimuthal_equidistant_projection(
    35., 265.)

SPC_DATES_KEY = 'spc_dates_unix_sec'
TEMP_FILE_NAMES_KEY = 'temp_file_names_key'
INPUT_FILE_NAMES_KEY = 'input_file_names_by_spc_date'
OUTPUT_FILE_NAMES_KEY = 'output_file_names_by_spc_date'

INTERMEDIATE_COLUMNS = [
    tracking_io.STORM_ID_COLUMN, tracking_io.ORIG_STORM_ID_COLUMN,
    tracking_io.TIME_COLUMN, tracking_io.SPC_DATE_COLUMN,
    best_tracks.CENTROID_X_COLUMN, best_tracks.CENTROID_Y_COLUMN,
    best_tracks.FILE_INDEX_COLUMN
]


def _read_intermediate_results(temp_file_name):
Exemple #4
0
LOCAL_MAX_COLUMNS = numpy.array([5, 5], dtype=int)
LOCAL_MAX_LATITUDES_DEG = numpy.array([34.96, 35])
LOCAL_MAX_LONGITUDES_DEG = numpy.array([95.1, 95.1])
LOCAL_MAX_VALUES = numpy.array([30, 6], dtype=float)

LOCAL_MAX_DICT_LATLNG = {
    temporal_tracking.LATITUDES_KEY: LOCAL_MAX_LATITUDES_DEG,
    temporal_tracking.LONGITUDES_KEY: LOCAL_MAX_LONGITUDES_DEG,
    echo_top_tracking.MAX_VALUES_KEY: LOCAL_MAX_VALUES
}

# The following constants are used to test _remove_redundant_local_maxima.
SMALL_INTERMAX_DISTANCE_METRES = 1000.
LARGE_INTERMAX_DISTANCE_METRES = 10000.

PROJECTION_OBJECT = projections.init_azimuthal_equidistant_projection(
    central_latitude_deg=35., central_longitude_deg=95.)

LOCAL_MAX_X_COORDS_METRES, LOCAL_MAX_Y_COORDS_METRES = (
    projections.project_latlng_to_xy(LOCAL_MAX_LATITUDES_DEG,
                                     LOCAL_MAX_LONGITUDES_DEG,
                                     projection_object=PROJECTION_OBJECT,
                                     false_easting_metres=0.,
                                     false_northing_metres=0.))

LOCAL_MAX_DICT_SMALL_DISTANCE = {
    temporal_tracking.LATITUDES_KEY: LOCAL_MAX_LATITUDES_DEG,
    temporal_tracking.LONGITUDES_KEY: LOCAL_MAX_LONGITUDES_DEG,
    echo_top_tracking.MAX_VALUES_KEY: LOCAL_MAX_VALUES,
    temporal_tracking.X_COORDS_KEY: LOCAL_MAX_X_COORDS_METRES,
    temporal_tracking.Y_COORDS_KEY: LOCAL_MAX_Y_COORDS_METRES
}
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
Exemple #6
0
    def test_init_azimuthal_equidistant_projection_no_crash(self):
        """Ensures that init_azimuthal_equidistant_projection does not crash."""

        projections.init_azimuthal_equidistant_projection(
            CENTRAL_LATITUDE_DEG, CENTRAL_LONGITUDE_DEG)
Exemple #7
0
def get_latlng_grid_points_in_radius(
        test_latitude_deg, test_longitude_deg, effective_radius_metres,
        grid_point_latitudes_deg=None, grid_point_longitudes_deg=None,
        grid_point_dict=None):
    """Finds lat-long grid points within radius of test point.

    One of the following sets of input args must be specified:

    - grid_point_latitudes_deg and grid_point_longitudes_deg
    - grid_point_dict

    M = number of rows (unique grid-point latitudes)
    N = number of columns (unique grid-point longitudes)
    K = number of grid points within radius of test point

    :param test_latitude_deg: Latitude (deg N) of test point.
    :param test_longitude_deg: Longitude (deg E) of test point.
    :param effective_radius_metres: Effective radius (will find all grid points
        within this radius of test point).
    :param grid_point_latitudes_deg: length-M numpy array with latitudes (deg N)
        of grid points.
    :param grid_point_longitudes_deg: length-N numpy array with longitudes
        (deg E) of grid points.
    :param grid_point_dict: Dictionary created by a previous run of this method
        (see output documentation).
    :return: rows_in_radius: length-K numpy array with row indices of grid
        points near test point.
    :return: columns_in_radius: Same but for columns.
    :return: grid_point_dict: Dictionary with the following keys.
    grid_point_dict['grid_point_x_matrix_metres']: M-by-N numpy array with
        x-coordinates of grid points.
    grid_point_dict['grid_point_y_matrix_metres']: M-by-N numpy array with
        y-coordinates of grid points.
    grid_point_dict['projection_object']: Instance of `pyproj.Proj`, which can
        be used to convert future test points from lat-long to x-y coordinates.
    """

    if grid_point_dict is None:
        (grid_point_lat_matrix_deg, grid_point_lng_matrix_deg
        ) = latlng_vectors_to_matrices(
            unique_latitudes_deg=grid_point_latitudes_deg,
            unique_longitudes_deg=grid_point_longitudes_deg)

        projection_object = projections.init_azimuthal_equidistant_projection(
            central_latitude_deg=numpy.mean(grid_point_latitudes_deg),
            central_longitude_deg=numpy.mean(grid_point_longitudes_deg))

        (grid_point_x_matrix_metres, grid_point_y_matrix_metres
        ) = projections.project_latlng_to_xy(
            latitudes_deg=grid_point_lat_matrix_deg,
            longitudes_deg=grid_point_lng_matrix_deg,
            projection_object=projection_object)

        grid_point_dict = {
            GRID_POINT_X_MATRIX_KEY: grid_point_x_matrix_metres,
            GRID_POINT_Y_MATRIX_KEY: grid_point_y_matrix_metres,
            PROJECTION_OBJECT_KEY: projection_object
        }

    error_checking.assert_is_valid_latitude(test_latitude_deg)
    error_checking.assert_is_geq(effective_radius_metres, 0.)
    test_longitude_deg = lng_conversion.convert_lng_positive_in_west(
        longitudes_deg=numpy.array([test_longitude_deg]), allow_nan=False)[0]

    (test_x_coords_metres, test_y_coords_metres
    ) = projections.project_latlng_to_xy(
        latitudes_deg=numpy.array([test_latitude_deg]),
        longitudes_deg=numpy.array([test_longitude_deg]),
        projection_object=grid_point_dict[PROJECTION_OBJECT_KEY])
    test_x_coord_metres = test_x_coords_metres[0]
    test_y_coord_metres = test_y_coords_metres[0]

    valid_x_flags = numpy.absolute(
        grid_point_dict[GRID_POINT_X_MATRIX_KEY] - test_x_coord_metres
    ) <= effective_radius_metres
    valid_y_flags = numpy.absolute(
        grid_point_dict[GRID_POINT_Y_MATRIX_KEY] - test_y_coord_metres
    ) <= effective_radius_metres
    rows_to_try, columns_to_try = numpy.where(numpy.logical_and(
        valid_x_flags, valid_y_flags))

    distances_to_try_metres = numpy.sqrt(
        (grid_point_dict[GRID_POINT_X_MATRIX_KEY][rows_to_try, columns_to_try] -
         test_x_coord_metres) ** 2 +
        (grid_point_dict[GRID_POINT_Y_MATRIX_KEY][rows_to_try, columns_to_try] -
         test_y_coord_metres) ** 2)
    valid_indices = numpy.where(
        distances_to_try_metres <= effective_radius_metres)[0]

    return (rows_to_try[valid_indices], columns_to_try[valid_indices],
            grid_point_dict)
Exemple #8
0
    def test_init_azimuthal_equidistant_projection_no_crash(self):
        """Ensures that init_azimuthal_equidistant_projection does not crash."""

        projections.init_azimuthal_equidistant_projection(
            central_latitude_deg=CENTRAL_LATITUDE_DEG,
            central_longitude_deg=CENTRAL_LONGITUDE_DEG)
if __name__ == '__main__':
    spc_date_strings = time_conversion.get_spc_dates_in_range(
        first_spc_date_string=FIRST_SPC_DATE_STRING,
        last_spc_date_string=LAST_SPC_DATE_STRING)
    num_spc_dates = len(spc_date_strings)
    storm_object_table_by_spc_date = [None] * num_spc_dates

    grid_point_lat_, grid_point_long = utils.get_latlng_grid_points(min_latitude_deg=MIN_LAT_DEG, min_longitude_deg=MIN_LONG_DEG, 
                                                     lat_spacing_deg= LATITUDE_SPACING_DEG, lng_spacing_deg=LONGITUDE_SPACING_DEG,
                                                        num_rows=NUM_ROWS, num_columns=NUM_COLUMNS)
    grid_point_lat = numpy.flip(grid_point_lat_,-1)
    lats, lons = latlng_vectors_to_matrices(grid_point_lat, grid_point_long)
    central_latitude_deg = numpy.mean(numpy.array([MIN_LAT_DEG, MAX_LAT_DEG]))
    central_longitude_deg = numpy.mean(numpy.array([MIN_LONG_DEG, MAX_LONG_DEG]))
     
    projection_object = projections.init_azimuthal_equidistant_projection(central_latitude_deg=central_latitude_deg,
                                                                          central_longitude_deg=central_longitude_deg)
    # Project lat-long grid points to x-y. 
    (grid_point_x_matrix_metres, grid_point_y_matrix_metres) = projections.project_latlng_to_xy(
                                                                                latitudes_deg=lats,
                                                                                longitudes_deg=lons,
                                                                                projection_object=projection_object)    
    
    x_min_metres = numpy.min(grid_point_x_matrix_metres)
    x_max_metres = numpy.max(grid_point_x_matrix_metres)
    y_min_metres = numpy.min(grid_point_y_matrix_metres)
    y_max_metres = numpy.max(grid_point_y_matrix_metres)
    
    # Round corners to nearest 10 km.  These will become the corners of the actual
    # x-y grid.
    x_min_metres = number_rounding.floor_to_nearest(x_min_metres, X_SPACING_METRES)
    x_max_metres = number_rounding.ceiling_to_nearest(x_max_metres, X_SPACING_METRES)
Exemple #10
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
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 _match_locations_one_time(source_object_table, target_object_table,
                              max_distance_metres):
    """Matches storm locations at one time.

    :param source_object_table: pandas DataFrame, where each row is a storm
        object in the source dataset.  See `storm_tracking_io.write_file` for a
        list of expected columns.
    :param target_object_table: Same but for target dataset.
    :param max_distance_metres: Max distance for matching.
    :return: source_to_target_dict: Dictionary, where each key is a tuple with
        (source ID, source time) and each value is a list with [target ID,
        target time].  The IDs are strings, and the times are Unix seconds
        (integers).  For source objects with no match in the target dataset, the
        corresponding value is None (rather than a list).
    """

    # TODO(thunderhoser): Maybe use polygons here?

    num_source_objects = len(source_object_table.index)
    source_to_target_dict = dict()

    if num_source_objects == 0:
        return source_to_target_dict

    for i in range(num_source_objects):
        this_key = (
            source_object_table[tracking_utils.FULL_ID_COLUMN].values[i],
            source_object_table[tracking_utils.VALID_TIME_COLUMN].values[i])

        source_to_target_dict[this_key] = None

    num_target_objects = len(target_object_table.index)
    if num_target_objects == 0:
        return source_to_target_dict

    # Create equidistant projection.
    all_latitudes_deg = numpy.concatenate(
        (source_object_table[tracking_utils.CENTROID_LATITUDE_COLUMN].values,
         target_object_table[tracking_utils.CENTROID_LATITUDE_COLUMN].values))
    all_longitudes_deg = numpy.concatenate(
        (source_object_table[tracking_utils.CENTROID_LONGITUDE_COLUMN].values,
         target_object_table[tracking_utils.CENTROID_LONGITUDE_COLUMN].values))
    projection_object = projections.init_azimuthal_equidistant_projection(
        central_latitude_deg=numpy.mean(all_latitudes_deg),
        central_longitude_deg=numpy.mean(all_longitudes_deg))

    # Project storm centers from lat-long to x-y.
    source_x_coords_metres, source_y_coords_metres = (
        projections.project_latlng_to_xy(
            latitudes_deg=source_object_table[
                tracking_utils.CENTROID_LATITUDE_COLUMN].values,
            longitudes_deg=source_object_table[
                tracking_utils.CENTROID_LONGITUDE_COLUMN].values,
            projection_object=projection_object))

    target_x_coords_metres, target_y_coords_metres = (
        projections.project_latlng_to_xy(
            latitudes_deg=target_object_table[
                tracking_utils.CENTROID_LATITUDE_COLUMN].values,
            longitudes_deg=target_object_table[
                tracking_utils.CENTROID_LONGITUDE_COLUMN].values,
            projection_object=projection_object))

    # Find nearest target object to each source object.
    source_coord_matrix = numpy.transpose(
        numpy.vstack((source_x_coords_metres, source_y_coords_metres)))
    target_coord_matrix = numpy.transpose(
        numpy.vstack((target_x_coords_metres, target_y_coords_metres)))

    distance_matrix_metres2 = euclidean_distances(X=source_coord_matrix,
                                                  Y=target_coord_matrix,
                                                  squared=True)

    nearest_target_indices = numpy.argmin(distance_matrix_metres2, axis=1)
    source_indices = numpy.linspace(0,
                                    num_source_objects - 1,
                                    num=num_source_objects,
                                    dtype=int)

    min_distances_metres2 = distance_matrix_metres2[source_indices,
                                                    nearest_target_indices]
    bad_subindices = numpy.where(
        min_distances_metres2 > max_distance_metres**2)[0]
    nearest_target_indices[bad_subindices] = -1

    # Print results to command window.
    num_matched_source_objects = numpy.sum(nearest_target_indices >= 0)
    source_time_string = time_conversion.unix_sec_to_string(
        source_object_table[tracking_utils.VALID_TIME_COLUMN].values[0],
        TIME_FORMAT)

    print('Matched {0:d} of {1:d} source objects at {2:s}.'.format(
        num_matched_source_objects, num_source_objects, source_time_string))

    # Fill dictionary.
    for i in range(num_source_objects):
        this_key = (
            source_object_table[tracking_utils.FULL_ID_COLUMN].values[i],
            source_object_table[tracking_utils.VALID_TIME_COLUMN].values[i])

        j = nearest_target_indices[i]
        if j == -1:
            continue

        source_to_target_dict[this_key] = [
            target_object_table[tracking_utils.FULL_ID_COLUMN].values[j],
            target_object_table[tracking_utils.VALID_TIME_COLUMN].values[j]
        ]

    return source_to_target_dict
Exemple #13
0
def create_equidistant_grid(min_latitude_deg,
                            max_latitude_deg,
                            min_longitude_deg,
                            max_longitude_deg,
                            x_spacing_metres,
                            y_spacing_metres,
                            azimuthal=True):
    """Creates equidistant grid.

    M = number of rows
    N = number of columns

    :param min_latitude_deg: Minimum latitude (deg N) in grid.
    :param max_latitude_deg: Max latitude (deg N) in grid.
    :param min_longitude_deg: Minimum longitude (deg E) in grid.
    :param max_longitude_deg: Max longitude (deg E) in grid.
    :param x_spacing_metres: Spacing between grid points in adjacent columns.
    :param y_spacing_metres: Spacing between grid points in adjacent rows.
    :param azimuthal: Boolean flag.  If True, will create azimuthal equidistant
        grid.  If False, will create Lambert conformal grid.
    :return: grid_dict: Dictionary with the following keys.
    grid_dict['grid_point_x_coords_metres']: length-N numpy array with unique
        x-coordinates at grid points.
    grid_dict['grid_point_y_coords_metres']: length-M numpy array with unique
        y-coordinates at grid points.
    grid_dict['projection_object']: Instance of `pyproj.Proj` (used to convert
        between lat-long coordinates and the x-y coordinates of the grid).
    """

    # Check input args.
    error_checking.assert_is_valid_latitude(min_latitude_deg)
    error_checking.assert_is_valid_latitude(max_latitude_deg)
    error_checking.assert_is_greater(max_latitude_deg, min_latitude_deg)
    error_checking.assert_is_greater(x_spacing_metres, 0.)
    error_checking.assert_is_greater(y_spacing_metres, 0.)
    error_checking.assert_is_boolean(azimuthal)

    min_longitude_deg = lng_conversion.convert_lng_negative_in_west(
        min_longitude_deg, allow_nan=False)
    max_longitude_deg = lng_conversion.convert_lng_negative_in_west(
        max_longitude_deg, allow_nan=False)
    error_checking.assert_is_greater(max_longitude_deg, min_longitude_deg)

    # Create lat-long grid.
    num_grid_rows = 1 + int(
        numpy.round((max_latitude_deg - min_latitude_deg) /
                    DUMMY_LATITUDE_SPACING_DEG))
    num_grid_columns = 1 + int(
        numpy.round((max_longitude_deg - min_longitude_deg) /
                    DUMMY_LONGITUDE_SPACING_DEG))

    unique_latitudes_deg, unique_longitudes_deg = get_latlng_grid_points(
        min_latitude_deg=min_latitude_deg,
        min_longitude_deg=min_longitude_deg,
        lat_spacing_deg=DUMMY_LATITUDE_SPACING_DEG,
        lng_spacing_deg=DUMMY_LONGITUDE_SPACING_DEG,
        num_rows=num_grid_rows,
        num_columns=num_grid_columns)

    latitude_matrix_deg, longitude_matrix_deg = latlng_vectors_to_matrices(
        unique_latitudes_deg=unique_latitudes_deg,
        unique_longitudes_deg=unique_longitudes_deg)

    # Create projection.
    central_latitude_deg = 0.5 * (min_latitude_deg + max_latitude_deg)
    central_longitude_deg = 0.5 * (min_longitude_deg + max_longitude_deg)

    if azimuthal:
        projection_object = projections.init_azimuthal_equidistant_projection(
            central_latitude_deg=central_latitude_deg,
            central_longitude_deg=central_longitude_deg)
    else:
        projection_object = projections.init_lcc_projection(
            standard_latitudes_deg=numpy.full(2, central_latitude_deg),
            central_longitude_deg=central_longitude_deg)

    # Convert lat-long grid to preliminary x-y grid.
    prelim_x_matrix_metres, prelim_y_matrix_metres = (
        projections.project_latlng_to_xy(latitudes_deg=latitude_matrix_deg,
                                         longitudes_deg=longitude_matrix_deg,
                                         projection_object=projection_object))

    # Find corners of preliminary x-y grid.
    x_min_metres = numpy.min(prelim_x_matrix_metres)
    x_max_metres = numpy.max(prelim_x_matrix_metres)
    y_min_metres = numpy.min(prelim_y_matrix_metres)
    y_max_metres = numpy.max(prelim_y_matrix_metres)

    # Find corners of final x-y grid.
    x_min_metres = number_rounding.floor_to_nearest(x_min_metres,
                                                    x_spacing_metres)
    x_max_metres = number_rounding.ceiling_to_nearest(x_max_metres,
                                                      x_spacing_metres)
    y_min_metres = number_rounding.floor_to_nearest(y_min_metres,
                                                    y_spacing_metres)
    y_max_metres = number_rounding.ceiling_to_nearest(y_max_metres,
                                                      y_spacing_metres)

    # Create final x-y grid.
    num_grid_rows = 1 + int(
        numpy.round((y_max_metres - y_min_metres) / y_spacing_metres))
    num_grid_columns = 1 + int(
        numpy.round((x_max_metres - x_min_metres) / x_spacing_metres))

    unique_x_coords_metres, unique_y_coords_metres = get_xy_grid_points(
        x_min_metres=x_min_metres,
        y_min_metres=y_min_metres,
        x_spacing_metres=x_spacing_metres,
        y_spacing_metres=y_spacing_metres,
        num_rows=num_grid_rows,
        num_columns=num_grid_columns)

    return {
        X_COORDS_KEY: unique_x_coords_metres,
        Y_COORDS_KEY: unique_y_coords_metres,
        PROJECTION_KEY: projection_object
    }
Exemple #14
0
def evaluate_tracks(storm_object_table, top_myrorss_dir_name,
                    radar_field_name):
    """Evaluates a set of storm tracks, following Lakshmanan and Smith (2010).

    T = number of storm tracks

    :param storm_object_table: pandas DataFrame with storm objects.  Should
        contain columns listed in `storm_tracking_io.write_file`.
    :param top_myrorss_dir_name: Name of top-level directory with MYRORSS data.
        Files therein will be found by `myrorss_and_mrms_io.find_raw_file` and
        read by `myrorss_and_mrms_io.read_data_from_sparse_grid_file`.
    :param radar_field_name: Name of radar field to use in computing mismatch
        error.  Must be accepted by `radar_utils.check_field_name`.
    :return: evaluation_dict: Dictionary with the following keys.
    evaluation_dict['track_durations_sec']: length-T numpy array of storm
        durations.
    evaluation_dict['track_linearity_errors_metres']: length-T numpy array of
        linearity errors.  The "linearity error" for one track is the RMSE
        (root mean square error) of Theil-Sen estimates over all time steps.
    evaluation_dict['track_mismatch_errors']: length-T numpy array of mismatch
        errors.  The "mismatch error" is the standard deviation of X over all
        time steps, where X = median value of `radar_field_name` inside the
        storm.
    evaluation_dict['mean_linearity_error_metres']: Mean linearity error.  This
        is the mean of trackwise linearity errors for tracks with duration >=
        median duration.
    evaluation_dict['mean_mismatch_error']: Mean mismatch error.  This is the
        mean of trackwise mismatch errors for tracks with duration >= median
        duration.
    evaluation_dict['radar_field_name']: Same as input (metadata).
    """

    # Add x-y coordinates.
    projection_object = projections.init_azimuthal_equidistant_projection(
        central_latitude_deg=echo_top_tracking.CENTRAL_PROJ_LATITUDE_DEG,
        central_longitude_deg=echo_top_tracking.CENTRAL_PROJ_LONGITUDE_DEG)

    x_coords_metres, y_coords_metres = projections.project_latlng_to_xy(
        latitudes_deg=storm_object_table[
            tracking_utils.CENTROID_LATITUDE_COLUMN].values,
        longitudes_deg=storm_object_table[
            tracking_utils.CENTROID_LONGITUDE_COLUMN].values,
        projection_object=projection_object,
        false_easting_metres=0.,
        false_northing_metres=0.)

    storm_object_table = storm_object_table.assign(
        **{
            tracking_utils.CENTROID_X_COLUMN: x_coords_metres,
            tracking_utils.CENTROID_Y_COLUMN: y_coords_metres
        })

    # Convert list of storm objects to list of tracks.
    storm_track_table = tracking_utils.storm_objects_to_tracks(
        storm_object_table)

    # Fit Theil-Sen model to each track.
    print(SEPARATOR_STRING)
    storm_track_table = _fit_theil_sen_many_tracks(storm_track_table)
    print(SEPARATOR_STRING)

    # Compute storm durations.
    num_tracks = len(storm_track_table.index)
    track_durations_sec = numpy.full(num_tracks, -1, dtype=int)

    for i in range(num_tracks):
        this_start_time_unix_sec = numpy.min(
            storm_track_table[tracking_utils.TRACK_TIMES_COLUMN].values[i])
        this_end_time_unix_sec = numpy.max(
            storm_track_table[tracking_utils.TRACK_TIMES_COLUMN].values[i])
        track_durations_sec[i] = (this_end_time_unix_sec -
                                  this_start_time_unix_sec)

    for this_percentile_level in DURATION_PERCENTILE_LEVELS:
        this_percentile_seconds = numpy.percentile(track_durations_sec,
                                                   this_percentile_level)

        print('{0:d}th percentile of track durations = {1:.1f} seconds'.format(
            int(numpy.round(this_percentile_level)), this_percentile_seconds))

    print('\n')

    for this_percentile_level in DURATION_PERCENTILE_LEVELS:
        this_percentile_seconds = numpy.percentile(
            track_durations_sec[track_durations_sec != 0],
            this_percentile_level)

        print(
            ('{0:d}th percentile of non-zero track durations = {1:.1f} seconds'
             ).format(int(numpy.round(this_percentile_level)),
                      this_percentile_seconds))

    print(SEPARATOR_STRING)

    median_duration_sec = numpy.median(
        track_durations_sec[track_durations_sec != 0])
    long_track_flags = track_durations_sec >= median_duration_sec
    long_track_indices = numpy.where(long_track_flags)[0]

    # Compute linearity error for each track.
    track_linearity_errors_metres = numpy.full(num_tracks, numpy.nan)

    for i in range(num_tracks):
        if numpy.mod(i, 50) == 0:
            print(('Have computed linearity error for {0:d} of {1:d} tracks...'
                   ).format(i, num_tracks))

        track_linearity_errors_metres[i] = _get_mean_ts_error_one_track(
            storm_track_table=storm_track_table, row_index=i)

    print('Have computed linearity error for all {0:d} tracks!'.format(
        num_tracks))

    good_indices = numpy.where(
        numpy.logical_and(
            long_track_flags,
            track_linearity_errors_metres <= MAX_LINEARITY_ERROR_METRES))[0]

    mean_linearity_error_metres = numpy.mean(
        track_linearity_errors_metres[good_indices])

    print('Mean linearity error = {0:.1f} metres'.format(
        mean_linearity_error_metres))
    print(SEPARATOR_STRING)

    # Compute mismatch error for each track.
    radar_statistic_table = (
        radar_stats.get_storm_based_radar_stats_myrorss_or_mrms(
            storm_object_table=storm_object_table,
            top_radar_dir_name=top_myrorss_dir_name,
            radar_metadata_dict_for_tracking=None,
            statistic_names=[],
            percentile_levels=numpy.array([50.]),
            radar_field_names=[radar_field_name],
            radar_source=radar_utils.MYRORSS_SOURCE_ID))

    print(SEPARATOR_STRING)

    radar_height_m_asl = radar_utils.get_valid_heights(
        data_source=radar_utils.MYRORSS_SOURCE_ID,
        field_name=radar_field_name)[0]

    median_column_name = radar_stats.radar_field_and_percentile_to_column_name(
        radar_field_name=radar_field_name,
        radar_height_m_asl=radar_height_m_asl,
        percentile_level=50.)

    median_by_storm_object = radar_statistic_table[median_column_name]
    track_mismatch_errors = numpy.full(num_tracks, numpy.nan)

    for i in range(num_tracks):
        these_object_indices = storm_track_table[
            tracking_utils.OBJECT_INDICES_COLUMN].values[i]

        if len(these_object_indices) < 2:
            continue

        track_mismatch_errors[i] = numpy.std(
            median_by_storm_object[these_object_indices], ddof=1)

    mean_mismatch_error = numpy.nanmean(
        track_mismatch_errors[long_track_indices])

    print('Mean mismatch error for "{0:s}" = {1:.4e}'.format(
        radar_field_name, mean_mismatch_error))

    return {
        DURATIONS_KEY: track_durations_sec,
        LINEARITY_ERRORS_KEY: track_linearity_errors_metres,
        MISMATCH_ERRORS_KEY: track_mismatch_errors,
        MEAN_LINEARITY_ERROR_KEY: mean_linearity_error_metres,
        MEAN_MISMATCH_ERROR_KEY: mean_mismatch_error,
        RADAR_FIELD_KEY: radar_field_name
    }