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):
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
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)
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)
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)
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
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 }
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 }