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 _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 project_latlng_to_xy(latitudes_deg, longitudes_deg, projection_object=None, model_name=None, grid_name=None): """Converts points from lat-long to x-y (model) coordinates. P = number of points :param latitudes_deg: length-P numpy array of latitudes (deg N). :param longitudes_deg: length-P numpy array of longitudes (deg E). :param projection_object: Instance of `pyproj.Proj`, specifying the projection. If None, will create projection object on the fly. :param model_name: See doc for `check_grid_name`. :param grid_name: Same. :return: x_coords_metres: length-P numpy array of x-coordinates. :return: y_coords_metres: length-P numpy array of x-coordinates. """ false_easting_metres, false_northing_metres = ( get_false_easting_and_northing(model_name=model_name, grid_name=grid_name)) if projection_object is None: projection_object = init_projection(model_name) return projections.project_latlng_to_xy( latitudes_deg=latitudes_deg, longitudes_deg=longitudes_deg, projection_object=projection_object, false_easting_metres=false_easting_metres, false_northing_metres=false_northing_metres)
def project_latlng_to_xy(latitudes_deg, longitudes_deg, projection_object=None, model_name=None, grid_id=None): """Converts from lat-long to x-y coordinates (under model projection). P = number of points to convert :param latitudes_deg: length-P numpy array of latitudes (deg N). :param longitudes_deg: length-P numpy array of longitudes (deg E). :param projection_object: Projection object created by init_model_projection. If projection_object = None, it will be created on the fly, based on args `model_name` and `grid_id`. :param model_name: Name of model. :param grid_id: ID for model grid. :return: x_coords_metres: length-P numpy array of x-coordinates. :return: y_coords_metres: length-P numpy array of y-coordinates. """ false_easting_metres, false_northing_metres = ( get_false_easting_and_northing(model_name, grid_id)) if projection_object is None: projection_object = init_model_projection(model_name) return projections.project_latlng_to_xy( latitudes_deg, longitudes_deg, projection_object=projection_object, false_easting_metres=false_easting_metres, false_northing_metres=false_northing_metres)
def _get_projection_offsets( basemap_object, pyproj_object, test_latitudes_deg, test_longitudes_deg): """Finds offsets between basemap and pyproj projections. P = number of points used to find offsets :param basemap_object: Instance of `mpl_toolkits.basemap.Basemap`. :param pyproj_object: Instance of `pyproj.Proj`. The two objects should encode the same projection, just with different false easting/northing. :param test_latitudes_deg: length-P numpy array of latitudes (deg N). :param test_longitudes_deg: length-P numpy array of longitudes (deg E). :return: x_offset_metres: x-offset (basemap minus pyproj). :return: y_offset_metres: y-offset (basemap minus pyproj). """ pyproj_x_coords_metres, pyproj_y_coords_metres = ( projections.project_latlng_to_xy( latitudes_deg=test_latitudes_deg, longitudes_deg=test_longitudes_deg, projection_object=pyproj_object) ) basemap_x_coords_metres, basemap_y_coords_metres = basemap_object( test_longitudes_deg, test_latitudes_deg) x_offset_metres = numpy.mean( basemap_x_coords_metres - pyproj_x_coords_metres ) y_offset_metres = numpy.mean( basemap_y_coords_metres - pyproj_y_coords_metres ) return x_offset_metres, y_offset_metres
def test_project_latlng_to_xy(self): """Ensures that project_latlng_to_xy does not crash. This is an integration test, not a unit test, because it requires init_lambert_conformal_projection to create the projection object. """ projection_object = projections.init_lambert_conformal_projection( STANDARD_LATITUDES_DEG, CENTRAL_LONGITUDE_DEG) projections.project_latlng_to_xy( LATITUDES_DEG, LONGITUDES_DEG, projection_object=projection_object, false_easting_metres=FALSE_EASTING_METRES, false_northing_metres=FALSE_NORTHING_METRES)
def _project_polygon_latlng_to_xy(polygon_object_latlng, centroid_latitude_deg=None, centroid_longitude_deg=None): """Projects polygon from lat-long to x-y coordinates. :param polygon_object_latlng: Instance of `shapely.geometry.Polygon`, where x-coordinates are actually longitudes and y-coordinates are actually latitudes. :param centroid_latitude_deg: Latitude (deg N) at polygon centroid. :param centroid_longitude_deg: Longitude (deg E) at polygon centroid. :return: polygon_object_xy: Instance of `shapely.geometry.Polygon`, where x- and y-coordinates are in metres. """ projection_object = projections.init_lambert_conformal_projection( standard_latitudes_deg=numpy.array( [centroid_latitude_deg, centroid_latitude_deg]), central_longitude_deg=centroid_longitude_deg) vertex_latitudes_deg = numpy.asarray(polygon_object_latlng.exterior.xy[1]) vertex_longitudes_deg = numpy.asarray(polygon_object_latlng.exterior.xy[0]) vertex_x_metres, vertex_y_metres = projections.project_latlng_to_xy( vertex_latitudes_deg, vertex_longitudes_deg, projection_object=projection_object, false_easting_metres=0., false_northing_metres=0.) return polygons.vertex_arrays_to_polygon_object(vertex_x_metres, vertex_y_metres)
def test_project_latlng_to_xy(self): """Ensures that project_latlng_to_xy does not crash. This is an integration test, not a unit test, because it requires init_lcc_projection to create the projection object. """ projection_object = projections.init_lcc_projection( standard_latitudes_deg=STANDARD_LATITUDES_DEG, central_longitude_deg=CENTRAL_LONGITUDE_DEG) projections.project_latlng_to_xy( latitudes_deg=LATITUDES_DEG, longitudes_deg=LONGITUDES_DEG, projection_object=projection_object, false_easting_metres=FALSE_EASTING_METRES, false_northing_metres=FALSE_NORTHING_METRES)
def _project_winds_latlng_to_xy(wind_table, projection_object): """Projects wind observations from lat-long to x-y coordinates. :param wind_table: pandas DataFrame created by _read_wind_observations. :param projection_object: Instance of `pyproj.Proj`, created by _init_azimuthal_equidistant_projection. :return: wind_table: Same as input, but with additional columns listed below. wind_table.x_coord_metres: x-coordinate. wind_table.y_coord_metres: y-coordinate. """ x_coords_metres, y_coords_metres = projections.project_latlng_to_xy( wind_table[raw_wind_io.LATITUDE_COLUMN].values, wind_table[raw_wind_io.LONGITUDE_COLUMN].values, projection_object=projection_object) argument_dict = { WIND_X_COLUMN: x_coords_metres, WIND_Y_COLUMN: y_coords_metres } return wind_table.assign(**argument_dict)
def test_project_both_ways(self): """Ensures that the two projection methods are inverses. This is an integration test, not a unit test, because it calls both projection methods. Also, it requires init_lcc_projection to create the projection object. """ projection_object = projections.init_lcc_projection( standard_latitudes_deg=STANDARD_LATITUDES_DEG, central_longitude_deg=CENTRAL_LONGITUDE_DEG) these_x_coords_metres, these_y_coords_metres = ( projections.project_latlng_to_xy( latitudes_deg=LATITUDES_DEG, longitudes_deg=LONGITUDES_DEG, projection_object=projection_object, false_easting_metres=FALSE_EASTING_METRES, false_northing_metres=FALSE_NORTHING_METRES)) these_latitudes_deg, these_longitudes_deg = ( projections.project_xy_to_latlng( x_coords_metres=these_x_coords_metres, y_coords_metres=these_y_coords_metres, projection_object=projection_object, false_easting_metres=FALSE_EASTING_METRES, false_northing_metres=FALSE_NORTHING_METRES)) self.assertTrue( numpy.allclose(these_latitudes_deg, LATITUDES_DEG, atol=TOLERANCE, equal_nan=True)) self.assertTrue( numpy.allclose(these_longitudes_deg, LONGITUDES_DEG, atol=TOLERANCE, equal_nan=True))
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 } LOCAL_MAX_DICT_LARGE_DISTANCE = { temporal_tracking.LATITUDES_KEY: LOCAL_MAX_LATITUDES_DEG[:-1], temporal_tracking.LONGITUDES_KEY: LOCAL_MAX_LONGITUDES_DEG[:-1], echo_top_tracking.MAX_VALUES_KEY: LOCAL_MAX_VALUES[:-1],
def _run(input_file_name, top_tracking_dir_name, min_latitude_deg, max_latitude_deg, min_longitude_deg, max_longitude_deg, grid_spacing_metres, output_dir_name): """Subsets ungridded predictions by space. This is effectively the main method. :param input_file_name: See documentation at top of file. :param top_tracking_dir_name: Same. :param min_latitude_deg: Same. :param max_latitude_deg: Same. :param min_longitude_deg: Same. :param max_longitude_deg: Same. :param grid_spacing_metres: Same. :param output_dir_name: Same. """ equidistant_grid_dict = grids.create_equidistant_grid( min_latitude_deg=min_latitude_deg, max_latitude_deg=max_latitude_deg, min_longitude_deg=min_longitude_deg, max_longitude_deg=max_longitude_deg, x_spacing_metres=grid_spacing_metres, y_spacing_metres=grid_spacing_metres, azimuthal=False) grid_metafile_name = grids.find_equidistant_metafile( directory_name=output_dir_name, raise_error_if_missing=False) print('Writing metadata for equidistant grid to: "{0:s}"...'.format( grid_metafile_name )) grids.write_equidistant_metafile(grid_dict=equidistant_grid_dict, pickle_file_name=grid_metafile_name) grid_point_x_coords_metres = equidistant_grid_dict[grids.X_COORDS_KEY] grid_point_y_coords_metres = equidistant_grid_dict[grids.Y_COORDS_KEY] projection_object = equidistant_grid_dict[grids.PROJECTION_KEY] grid_edge_x_coords_metres = numpy.append( grid_point_x_coords_metres - 0.5 * grid_spacing_metres, grid_point_x_coords_metres[-1] + 0.5 * grid_spacing_metres ) grid_edge_y_coords_metres = numpy.append( grid_point_y_coords_metres - 0.5 * grid_spacing_metres, grid_point_y_coords_metres[-1] + 0.5 * grid_spacing_metres ) print('Reading input data from: "{0:s}"...'.format(input_file_name)) prediction_dict = prediction_io.read_ungridded_predictions(input_file_name) print(SEPARATOR_STRING) full_id_strings = prediction_dict[prediction_io.STORM_IDS_KEY] storm_times_unix_sec = prediction_dict[prediction_io.STORM_TIMES_KEY] unique_storm_times_unix_sec = numpy.unique(storm_times_unix_sec) num_storm_objects = len(storm_times_unix_sec) storm_latitudes_deg = numpy.full(num_storm_objects, numpy.nan) storm_longitudes_deg = numpy.full(num_storm_objects, numpy.nan) for this_time_unix_sec in unique_storm_times_unix_sec: these_indices = numpy.where( storm_times_unix_sec == this_time_unix_sec )[0] these_full_id_strings = [full_id_strings[k] for k in these_indices] (storm_latitudes_deg[these_indices], storm_longitudes_deg[these_indices] ) = _read_storm_locations_one_time( top_tracking_dir_name=top_tracking_dir_name, valid_time_unix_sec=this_time_unix_sec, desired_full_id_strings=these_full_id_strings) print(SEPARATOR_STRING) storm_x_coords_metres, storm_y_coords_metres = ( projections.project_latlng_to_xy( latitudes_deg=storm_latitudes_deg, longitudes_deg=storm_longitudes_deg, projection_object=projection_object) ) num_grid_rows = len(grid_point_y_coords_metres) num_grid_columns = len(grid_point_x_coords_metres) for i in range(num_grid_rows): for j in range(num_grid_columns): these_indices = grids.find_events_in_grid_cell( event_x_coords_metres=storm_x_coords_metres, event_y_coords_metres=storm_y_coords_metres, grid_edge_x_coords_metres=grid_edge_x_coords_metres, grid_edge_y_coords_metres=grid_edge_y_coords_metres, row_index=i, column_index=j, verbose=True) if len(these_indices) == 0: continue this_prediction_dict = prediction_io.subset_ungridded_predictions( prediction_dict=prediction_dict, desired_storm_indices=these_indices) this_output_file_name = prediction_io.find_ungridded_file( directory_name=output_dir_name, grid_row=i, grid_column=j, raise_error_if_missing=False) print('Writing subset to: "{0:s}"...'.format(this_output_file_name)) prediction_io.write_ungridded_predictions( netcdf_file_name=this_output_file_name, class_probability_matrix=this_prediction_dict[ prediction_io.PROBABILITY_MATRIX_KEY], storm_ids=this_prediction_dict[prediction_io.STORM_IDS_KEY], storm_times_unix_sec=this_prediction_dict[ prediction_io.STORM_TIMES_KEY], observed_labels=this_prediction_dict[ prediction_io.OBSERVED_LABELS_KEY], target_name=this_prediction_dict[prediction_io.TARGET_NAME_KEY], model_file_name=this_prediction_dict[ prediction_io.MODEL_FILE_KEY] ) print('\n')
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 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 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 _shuffle_data_with_smart_io(storm_object_table=None, file_dict=None, working_spc_date_unix_sec=None, read_from_intermediate=None): """Shuffles data with smart IO. Specifically, this method ensures that only SPC dates (k - 1)...(k + 1) are in memory, where k is the date currently being worked on. :param storm_object_table: pandas DataFrame with columns documented in _write_intermediate_results. :param file_dict: See documentation for find_files_for_smart_io.. :param working_spc_date_unix_sec: Next SPC date to work on. :param read_from_intermediate: Boolean flag. If True, will read from intermediate files. If False, will read from input files. :return: storm_object_table: pandas DataFrame with columns documented in _write_intermediate_results. """ working_spc_date_index = numpy.where( file_dict[SPC_DATES_KEY] == working_spc_date_unix_sec)[0][0] num_spc_dates = len(file_dict[SPC_DATES_KEY]) if working_spc_date_index == 0: read_spc_date_indices = numpy.array([0, 1], dtype=int) write_spc_date_indices = numpy.array( [num_spc_dates - 2, num_spc_dates - 1], dtype=int) clear_table = True elif working_spc_date_index == num_spc_dates - 1: read_spc_date_indices = numpy.array([], dtype=int) write_spc_date_indices = numpy.array([num_spc_dates - 3], dtype=int) clear_table = False else: read_spc_date_indices = numpy.array([working_spc_date_index + 1], dtype=int) write_spc_date_indices = numpy.array([working_spc_date_index - 2], dtype=int) clear_table = False read_spc_date_indices = read_spc_date_indices[read_spc_date_indices >= 0] read_spc_date_indices = read_spc_date_indices[ read_spc_date_indices < num_spc_dates] write_spc_date_indices = write_spc_date_indices[ write_spc_date_indices >= 0] write_spc_date_indices = write_spc_date_indices[ write_spc_date_indices < num_spc_dates] if storm_object_table is not None: for this_index in write_spc_date_indices: this_spc_date_unix_sec = file_dict[SPC_DATES_KEY][this_index] this_spc_date_string = time_conversion.time_to_spc_date_string( this_spc_date_unix_sec) this_spc_date_indices = numpy.where( storm_object_table[tracking_io.SPC_DATE_COLUMN].values == this_spc_date_unix_sec)[0] this_temp_file_name = file_dict[TEMP_FILE_NAMES_KEY][this_index] print('Writing intermediate data for ' + this_spc_date_string + ': ' + this_temp_file_name + '...') _write_intermediate_results( storm_object_table.iloc[this_spc_date_indices], this_temp_file_name) storm_object_table.drop( storm_object_table.index[this_spc_date_indices], axis=0, inplace=True) if clear_table: storm_object_table = None for this_index in read_spc_date_indices: this_spc_date_unix_sec = file_dict[SPC_DATES_KEY][this_index] this_spc_date_string = time_conversion.time_to_spc_date_string( this_spc_date_unix_sec) if read_from_intermediate: this_temp_file_name = file_dict[TEMP_FILE_NAMES_KEY][this_index] print('Reading intermediate data for ' + this_spc_date_string + ': ' + this_temp_file_name + '...') this_storm_object_table = _read_intermediate_results( this_temp_file_name) else: this_storm_object_table = best_tracks.read_input_storm_objects( file_dict[INPUT_FILE_NAMES_KEY][this_index], keep_spc_date=True) these_centroid_x_metres, these_centroid_y_metres = ( projections.project_latlng_to_xy( this_storm_object_table[ tracking_io.CENTROID_LAT_COLUMN].values, this_storm_object_table[ tracking_io.CENTROID_LNG_COLUMN].values, projection_object=PROJECTION_OBJECT, false_easting_metres=0., false_northing_metres=0.)) argument_dict = { best_tracks.CENTROID_X_COLUMN: these_centroid_x_metres, best_tracks.CENTROID_Y_COLUMN: these_centroid_y_metres } this_storm_object_table = this_storm_object_table.assign( **argument_dict) this_storm_object_table.drop([ tracking_io.CENTROID_LAT_COLUMN, tracking_io.CENTROID_LNG_COLUMN ], axis=1, inplace=True) if storm_object_table is None: storm_object_table = copy.deepcopy(this_storm_object_table) else: this_storm_object_table, _ = this_storm_object_table.align( storm_object_table, axis=1) storm_object_table = pandas.concat( [storm_object_table, this_storm_object_table], axis=0, ignore_index=True) return storm_object_table
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) 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) num_grid_rows = 1 + int(numpy.round((y_max_metres - y_min_metres) / Y_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 _get_grid_point_coords(model_name, first_row_in_full_grid, last_row_in_full_grid, first_column_in_full_grid, last_column_in_full_grid, grid_id=None, basemap_object=None): """Returns x-y and lat-long coords for a subgrid of the full model grid. This method generates different x-y coordinates than `nwp_model_utils.get_xy_grid_point_matrices`, because (like `mpl_toolkits.basemap.Basemap`) this method sets false easting = false northing = 0 metres. :param model_name: Name of NWP model (must be accepted by `nwp_model_utils.check_grid_name`). :param first_row_in_full_grid: Row 0 in the subgrid is row `first_row_in_full_grid` in the full grid. :param last_row_in_full_grid: Last row in the subgrid is row `last_row_in_full_grid` in the full grid. If you want last row in the subgrid to equal last row in the full grid, make this -1. :param first_column_in_full_grid: Column 0 in the subgrid is column `first_column_in_full_grid` in the full grid. :param last_column_in_full_grid: Last column in the subgrid is column `last_column_in_full_grid` in the full grid. If you want last column in the subgrid to equal last column in the full grid, make this -1. :param grid_id: Grid for NWP model (must be accepted by `nwp_model_utils.check_grid_name`). :param basemap_object: Instance of `mpl_toolkits.basemap.Basemap` for the given NWP model. If you don't have one, no big deal -- leave this argument empty. :return: coordinate_dict: Dictionary with the following keys. coordinate_dict['grid_point_x_matrix_metres']: M-by-N numpy array of x-coordinates. coordinate_dict['grid_point_y_matrix_metres']: M-by-N numpy array of y-coordinates. coordinate_dict['grid_point_lat_matrix_deg']: M-by-N numpy array of latitudes (deg N). coordinate_dict['grid_point_lng_matrix_deg']: M-by-N numpy array of longitudes (deg E). """ num_rows_in_full_grid, num_columns_in_full_grid = ( nwp_model_utils.get_grid_dimensions(model_name=model_name, grid_name=grid_id)) error_checking.assert_is_integer(first_row_in_full_grid) error_checking.assert_is_geq(first_row_in_full_grid, 0) error_checking.assert_is_integer(last_row_in_full_grid) if last_row_in_full_grid < 0: last_row_in_full_grid += num_rows_in_full_grid error_checking.assert_is_greater(last_row_in_full_grid, first_row_in_full_grid) error_checking.assert_is_less_than(last_row_in_full_grid, num_rows_in_full_grid) error_checking.assert_is_integer(first_column_in_full_grid) error_checking.assert_is_geq(first_column_in_full_grid, 0) error_checking.assert_is_integer(last_column_in_full_grid) if last_column_in_full_grid < 0: last_column_in_full_grid += num_columns_in_full_grid error_checking.assert_is_greater(last_column_in_full_grid, first_column_in_full_grid) error_checking.assert_is_less_than(last_column_in_full_grid, num_columns_in_full_grid) grid_point_lat_matrix_deg, grid_point_lng_matrix_deg = ( nwp_model_utils.get_latlng_grid_point_matrices(model_name=model_name, grid_name=grid_id)) grid_point_lat_matrix_deg = grid_point_lat_matrix_deg[ first_row_in_full_grid:(last_row_in_full_grid + 1), first_column_in_full_grid:(last_column_in_full_grid + 1)] grid_point_lng_matrix_deg = grid_point_lng_matrix_deg[ first_row_in_full_grid:(last_row_in_full_grid + 1), first_column_in_full_grid:(last_column_in_full_grid + 1)] if basemap_object is None: standard_latitudes_deg, central_longitude_deg = ( nwp_model_utils.get_projection_params(model_name)) projection_object = projections.init_lcc_projection( standard_latitudes_deg=standard_latitudes_deg, central_longitude_deg=central_longitude_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, false_northing_metres=0., false_easting_metres=0.)) else: grid_point_x_matrix_metres, grid_point_y_matrix_metres = basemap_object( grid_point_lng_matrix_deg, grid_point_lat_matrix_deg) return { X_COORD_MATRIX_KEY: grid_point_x_matrix_metres, Y_COORD_MATRIX_KEY: grid_point_y_matrix_metres, LATITUDE_MATRIX_KEY: grid_point_lat_matrix_deg, LONGITUDE_MATRIX_KEY: grid_point_lng_matrix_deg, }
def _run(prediction_file_name, top_tracking_dir_name, prob_threshold, grid_spacing_metres, output_dir_name): """Plots spatial distribution of false alarms. This is effectively the main method. :param prediction_file_name: See documentation at top of file. :param top_tracking_dir_name: Same. :param prob_threshold: Same. :param grid_spacing_metres: Same. :param output_dir_name: Same. """ # Process input args. file_system_utils.mkdir_recursive_if_necessary( directory_name=output_dir_name) error_checking.assert_is_greater(prob_threshold, 0.) error_checking.assert_is_less_than(prob_threshold, 1.) grid_metadata_dict = grids.create_equidistant_grid( min_latitude_deg=MIN_LATITUDE_DEG, max_latitude_deg=MAX_LATITUDE_DEG, min_longitude_deg=MIN_LONGITUDE_DEG, max_longitude_deg=MAX_LONGITUDE_DEG, x_spacing_metres=grid_spacing_metres, y_spacing_metres=grid_spacing_metres, azimuthal=False) # Read predictions and find positive forecasts and false alarms. print('Reading predictions from: "{0:s}"...'.format(prediction_file_name)) prediction_dict = prediction_io.read_ungridded_predictions( prediction_file_name) observed_labels = prediction_dict[prediction_io.OBSERVED_LABELS_KEY] forecast_labels = ( prediction_dict[prediction_io.PROBABILITY_MATRIX_KEY][:, -1] >= prob_threshold).astype(int) pos_forecast_indices = numpy.where(forecast_labels == 1)[0] false_alarm_indices = numpy.where( numpy.logical_and(observed_labels == 0, forecast_labels == 1))[0] num_examples = len(observed_labels) num_positive_forecasts = len(pos_forecast_indices) num_false_alarms = len(false_alarm_indices) print(('Probability threshold = {0:.3f} ... number of examples, positive ' 'forecasts, false alarms = {1:d}, {2:d}, {3:d}').format( prob_threshold, num_examples, num_positive_forecasts, num_false_alarms)) # Find and read tracking files. pos_forecast_id_strings = [ prediction_dict[prediction_io.STORM_IDS_KEY][k] for k in pos_forecast_indices ] pos_forecast_times_unix_sec = ( prediction_dict[prediction_io.STORM_TIMES_KEY][pos_forecast_indices]) file_times_unix_sec = numpy.unique(pos_forecast_times_unix_sec) num_files = len(file_times_unix_sec) storm_object_tables = [None] * num_files print(SEPARATOR_STRING) for i in range(num_files): this_tracking_file_name = tracking_io.find_file( top_tracking_dir_name=top_tracking_dir_name, tracking_scale_metres2=DUMMY_TRACKING_SCALE_METRES2, source_name=tracking_utils.SEGMOTION_NAME, valid_time_unix_sec=file_times_unix_sec[i], spc_date_string=time_conversion.time_to_spc_date_string( file_times_unix_sec[i]), raise_error_if_missing=True) print('Reading data from: "{0:s}"...'.format(this_tracking_file_name)) this_table = tracking_io.read_file(this_tracking_file_name) storm_object_tables[i] = this_table.loc[this_table[ tracking_utils.FULL_ID_COLUMN].isin(pos_forecast_id_strings)] if i == 0: continue storm_object_tables[i] = storm_object_tables[i].align( storm_object_tables[0], axis=1)[0] storm_object_table = pandas.concat(storm_object_tables, axis=0, ignore_index=True) print(SEPARATOR_STRING) # Find latitudes and longitudes of false alarms. all_id_strings = ( storm_object_table[tracking_utils.FULL_ID_COLUMN].values.tolist()) all_times_unix_sec = ( storm_object_table[tracking_utils.VALID_TIME_COLUMN].values) good_indices = tracking_utils.find_storm_objects( all_id_strings=all_id_strings, all_times_unix_sec=all_times_unix_sec, id_strings_to_keep=pos_forecast_id_strings, times_to_keep_unix_sec=pos_forecast_times_unix_sec, allow_missing=False) pos_forecast_latitudes_deg = storm_object_table[ tracking_utils.CENTROID_LATITUDE_COLUMN].values[good_indices] pos_forecast_longitudes_deg = storm_object_table[ tracking_utils.CENTROID_LONGITUDE_COLUMN].values[good_indices] false_alarm_id_strings = [ prediction_dict[prediction_io.STORM_IDS_KEY][k] for k in false_alarm_indices ] false_alarm_times_unix_sec = ( prediction_dict[prediction_io.STORM_TIMES_KEY][false_alarm_indices]) good_indices = tracking_utils.find_storm_objects( all_id_strings=all_id_strings, all_times_unix_sec=all_times_unix_sec, id_strings_to_keep=false_alarm_id_strings, times_to_keep_unix_sec=false_alarm_times_unix_sec, allow_missing=False) false_alarm_latitudes_deg = storm_object_table[ tracking_utils.CENTROID_LATITUDE_COLUMN].values[good_indices] false_alarm_longitudes_deg = storm_object_table[ tracking_utils.CENTROID_LONGITUDE_COLUMN].values[good_indices] pos_forecast_x_coords_metres, pos_forecast_y_coords_metres = ( projections.project_latlng_to_xy( latitudes_deg=pos_forecast_latitudes_deg, longitudes_deg=pos_forecast_longitudes_deg, projection_object=grid_metadata_dict[grids.PROJECTION_KEY])) num_pos_forecasts_matrix = grids.count_events_on_equidistant_grid( event_x_coords_metres=pos_forecast_x_coords_metres, event_y_coords_metres=pos_forecast_y_coords_metres, grid_point_x_coords_metres=grid_metadata_dict[grids.X_COORDS_KEY], grid_point_y_coords_metres=grid_metadata_dict[grids.Y_COORDS_KEY])[0] print(SEPARATOR_STRING) false_alarm_x_coords_metres, false_alarm_y_coords_metres = ( projections.project_latlng_to_xy( latitudes_deg=false_alarm_latitudes_deg, longitudes_deg=false_alarm_longitudes_deg, projection_object=grid_metadata_dict[grids.PROJECTION_KEY])) num_false_alarms_matrix = grids.count_events_on_equidistant_grid( event_x_coords_metres=false_alarm_x_coords_metres, event_y_coords_metres=false_alarm_y_coords_metres, grid_point_x_coords_metres=grid_metadata_dict[grids.X_COORDS_KEY], grid_point_y_coords_metres=grid_metadata_dict[grids.Y_COORDS_KEY])[0] print(SEPARATOR_STRING) num_pos_forecasts_matrix = num_pos_forecasts_matrix.astype(float) num_pos_forecasts_matrix[num_pos_forecasts_matrix == 0] = numpy.nan num_false_alarms_matrix = num_false_alarms_matrix.astype(float) num_false_alarms_matrix[num_false_alarms_matrix == 0] = numpy.nan far_matrix = num_false_alarms_matrix / num_pos_forecasts_matrix this_max_value = numpy.nanpercentile(num_false_alarms_matrix, MAX_COUNT_PERCENTILE_TO_PLOT) if this_max_value < 10: this_max_value = numpy.nanmax(num_false_alarms_matrix) figure_object = plotter._plot_one_value( data_matrix=num_false_alarms_matrix, grid_metadata_dict=grid_metadata_dict, colour_map_object=CMAP_OBJECT_FOR_COUNTS, min_colour_value=0, max_colour_value=this_max_value, plot_cbar_min_arrow=False, plot_cbar_max_arrow=True)[0] num_false_alarms_file_name = '{0:s}/num_false_alarms.jpg'.format( output_dir_name) print('Saving figure to: "{0:s}"...'.format(num_false_alarms_file_name)) figure_object.savefig(num_false_alarms_file_name, dpi=FIGURE_RESOLUTION_DPI, pad_inches=0, bbox_inches='tight') pyplot.close(figure_object) this_max_value = numpy.nanpercentile(num_pos_forecasts_matrix, MAX_COUNT_PERCENTILE_TO_PLOT) if this_max_value < 10: this_max_value = numpy.nanmax(num_pos_forecasts_matrix) figure_object = plotter._plot_one_value( data_matrix=num_pos_forecasts_matrix, grid_metadata_dict=grid_metadata_dict, colour_map_object=CMAP_OBJECT_FOR_COUNTS, min_colour_value=0, max_colour_value=this_max_value, plot_cbar_min_arrow=False, plot_cbar_max_arrow=True)[0] num_pos_forecasts_file_name = '{0:s}/num_positive_forecasts.jpg'.format( output_dir_name) print('Saving figure to: "{0:s}"...'.format(num_pos_forecasts_file_name)) figure_object.savefig(num_pos_forecasts_file_name, dpi=FIGURE_RESOLUTION_DPI, pad_inches=0, bbox_inches='tight') pyplot.close(figure_object) this_max_value = numpy.nanpercentile(far_matrix, MAX_FAR_PERCENTILE_TO_PLOT) this_min_value = numpy.nanpercentile(far_matrix, 100. - MAX_FAR_PERCENTILE_TO_PLOT) figure_object = plotter._plot_one_value( data_matrix=far_matrix, grid_metadata_dict=grid_metadata_dict, colour_map_object=CMAP_OBJECT_FOR_FAR, min_colour_value=this_min_value, max_colour_value=this_max_value, plot_cbar_min_arrow=this_min_value > 0., plot_cbar_max_arrow=this_max_value < 1.)[0] far_file_name = '{0:s}/false_alarm_ratio.jpg'.format(output_dir_name) print('Saving figure to: "{0:s}"...'.format(far_file_name)) figure_object.savefig(far_file_name, dpi=FIGURE_RESOLUTION_DPI, pad_inches=0, bbox_inches='tight') pyplot.close(figure_object)
def get_xy_grid_point_matrices(first_row_in_narr_grid, last_row_in_narr_grid, first_column_in_narr_grid, last_column_in_narr_grid, basemap_object=None): """Returns coordinate matrices for a contiguous subset of the NARR grid. However, this subset need not be *strictly* a subset. In other words, the "subset" could be the full NARR grid. This method generates different x- and y-coordinates than `nwp_model_utils.get_xy_grid_point_matrices`, because (like `mpl_toolkits.basemap.Basemap`) this method assumes that false easting and northing are zero. :param first_row_in_narr_grid: Row 0 in the subgrid is row `first_row_in_narr_grid` in the full NARR grid. :param last_row_in_narr_grid: Last row (index -1) in the subgrid is row `last_row_in_narr_grid` in the full NARR grid. :param first_column_in_narr_grid: Column 0 in the subgrid is row `first_column_in_narr_grid` in the full NARR grid. :param last_column_in_narr_grid: Last column (index -1) in the subgrid is row `last_column_in_narr_grid` in the full NARR grid. :param basemap_object: Instance of `mpl_toolkits.basemap.Basemap` created for the NARR grid. If you don't have one, no big deal -- leave this argument empty. :return: grid_point_x_matrix_metres: M-by-N numpy array of x-coordinates. :return: grid_point_y_matrix_metres: M-by-N numpy array of y-coordinates. """ error_checking.assert_is_integer(first_row_in_narr_grid) error_checking.assert_is_geq(first_row_in_narr_grid, 0) error_checking.assert_is_integer(last_row_in_narr_grid) error_checking.assert_is_greater(last_row_in_narr_grid, first_row_in_narr_grid) error_checking.assert_is_less_than(last_row_in_narr_grid, NUM_ROWS_IN_NARR_GRID) error_checking.assert_is_integer(first_column_in_narr_grid) error_checking.assert_is_geq(first_column_in_narr_grid, 0) error_checking.assert_is_integer(last_column_in_narr_grid) error_checking.assert_is_greater(last_column_in_narr_grid, first_column_in_narr_grid) error_checking.assert_is_less_than(last_column_in_narr_grid, NUM_COLUMNS_IN_NARR_GRID) latitude_matrix_deg, longitude_matrix_deg = ( nwp_model_utils.get_latlng_grid_point_matrices( model_name=nwp_model_utils.NARR_MODEL_NAME)) latitude_matrix_deg = latitude_matrix_deg[first_row_in_narr_grid:( last_row_in_narr_grid + 1), first_column_in_narr_grid:(last_column_in_narr_grid + 1)] longitude_matrix_deg = longitude_matrix_deg[first_row_in_narr_grid:( last_row_in_narr_grid + 1), first_column_in_narr_grid:(last_column_in_narr_grid + 1)] if basemap_object is None: standard_latitudes_deg, central_longitude_deg = ( nwp_model_utils.get_projection_params( nwp_model_utils.NARR_MODEL_NAME)) projection_object = projections.init_lambert_conformal_projection( standard_latitudes_deg=standard_latitudes_deg, central_longitude_deg=central_longitude_deg) grid_point_x_matrix_metres, grid_point_y_matrix_metres = ( projections.project_latlng_to_xy( latitude_matrix_deg, longitude_matrix_deg, projection_object=projection_object, false_northing_metres=0., false_easting_metres=0.)) else: grid_point_x_matrix_metres, grid_point_y_matrix_metres = ( basemap_object(longitude_matrix_deg, latitude_matrix_deg)) return grid_point_x_matrix_metres, grid_point_y_matrix_metres
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 }