def test_buffer_simple_polygon_large(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer distance is larger. """ this_buffer_polygon_object = polygons.buffer_simple_polygon( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_polygon_object) this_buffer_vertex_x_metres, this_buffer_vertex_y_metres = ( polygons.merge_exterior_and_holes( this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) self.assertTrue( numpy.allclose(this_buffer_vertex_x_metres, VERTEX_X_METRES_LARGE_BUFFER, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_y_metres, VERTEX_Y_METRES_LARGE_BUFFER, atol=TOLERANCE))
def test_buffer_simple_polygon_large_inclusive(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer is large and inclusive (includes original polygon). """ this_buffered_polygon_object = polygons.buffer_simple_polygon( vertex_x_metres=EXTERIOR_VERTEX_X_METRES, vertex_y_metres=EXTERIOR_VERTEX_Y_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffered_polygon_object) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], LARGE_BUFFER_VERTEX_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], LARGE_BUFFER_VERTEX_Y_METRES, atol=TOLERANCE)) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_X_COLUMN]) == 0) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN]) == 0)
def test_buffer_simple_polygon_exclusive(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer is exclusive (does not include original polygon). """ this_buffer_polygon_object = polygons.buffer_simple_polygon( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, min_buffer_dist_metres=SMALL_BUFFER_DIST_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_polygon_object) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], EXCLUSIVE_BUFFER_EXTERIOR_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], EXCLUSIVE_BUFFER_EXTERIOR_Y_METRES, atol=TOLERANCE)) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_X_COLUMN]) == 1) self.assertTrue( len(this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN]) == 1) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.HOLE_X_COLUMN][0], EXCLUSIVE_BUFFER_HOLE_X_METRES, atol=TOLERANCE)) self.assertTrue( numpy.allclose(this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN][0], EXCLUSIVE_BUFFER_HOLE_Y_METRES, atol=TOLERANCE))
def test_buffer_simple_polygon_nested(self): """Ensures correct output from buffer_simple_polygon. In this case the buffer is nested (large distance used to create exterior, small distance used to create hole). """ this_buffer_polygon_object = polygons.buffer_simple_polygon( EXTERIOR_VERTEX_X_METRES, EXTERIOR_VERTEX_Y_METRES, min_buffer_dist_metres=SMALL_BUFFER_DIST_METRES, max_buffer_dist_metres=LARGE_BUFFER_DIST_METRES, preserve_angles=True) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_polygon_object) this_buffer_vertex_x_metres, this_buffer_vertex_y_metres = ( polygons.merge_exterior_and_holes( this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN], this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) self.assertTrue( numpy.allclose(this_buffer_vertex_x_metres, VERTEX_X_METRES_NESTED_BUFFER, atol=TOLERANCE, equal_nan=True)) self.assertTrue( numpy.allclose(this_buffer_vertex_y_metres, VERTEX_Y_METRES_NESTED_BUFFER, atol=TOLERANCE, equal_nan=True))
LIVE_SELECTED_INDICES_FOR_MIN_OBS_SAMPLING = numpy.array( [5, 6, 9, 10, 12, 13, 14, 15, 18, 20, 21]) LIVE_SELECTED_INDICES_FOR_MIN_OBS_PLUS_SAMPLING = numpy.array( [5, 6, 9, 10, 12, 13, 14, 15, 18, 20, 21, 23, 24]) LIVE_SELECTED_INDICES_FOR_MIN_DENSITY_SAMPLING = copy.deepcopy( LIVE_SELECTED_INDICES_FOR_MIN_OBS_SAMPLING) LIVE_SELECTED_INDICES_FOR_MIN_DENSITY_PLUS_SAMPLING = copy.deepcopy( LIVE_SELECTED_INDICES_FOR_MIN_OBS_PLUS_SAMPLING) # The following constants are used to test _get_observation_densities. POLYGON_OBJECT_XY, THIS_PROJECTION_OBJECT = polygons.project_latlng_to_xy( POLYGON_OBJECT_LATLNG) VERTEX_DICT_XY = polygons.polygon_object_to_vertex_arrays(POLYGON_OBJECT_XY) BUFFERED_POLYGON_OBJECT_XY = polygons.buffer_simple_polygon( VERTEX_DICT_XY[polygons.EXTERIOR_X_COLUMN], VERTEX_DICT_XY[polygons.EXTERIOR_Y_COLUMN], min_buffer_dist_metres=MIN_DISTANCE_METRES, max_buffer_dist_metres=MAX_DISTANCE_METRES) BUFFER_AREA_METRES2 = BUFFERED_POLYGON_OBJECT_XY.area OBSERVATION_DENSITY_BY_STORM_OBJECT_M02 = (NUM_OBSERVATIONS_BY_STORM_OBJECT / BUFFER_AREA_METRES2) MIN_OBS_DENSITY_FOR_SAMPLING_M02 = (MIN_OBSERVATIONS_FOR_SAMPLING / BUFFER_AREA_METRES2) BUFFERED_POLYGON_OBJECT_LATLNG = polygons.project_xy_to_latlng( BUFFERED_POLYGON_OBJECT_XY, THIS_PROJECTION_OBJECT) BUFFERED_POLYGON_OBJECT_ARRAY_LATLNG = numpy.full( NUM_STORM_OBJECTS, BUFFERED_POLYGON_OBJECT_LATLNG, dtype=object) BUFFER_COLUMN_NAME = tracking_io.distance_buffer_to_column_name(
def create_distance_buffers(storm_object_table, min_distances_metres, max_distances_metres): """Creates one or more distance buffers around each storm object. K = number of buffers :param storm_object_table: pandas DataFrame with the following columns. Each row is one storm object. storm_object_table.centroid_latitude_deg: Latitude (deg N) of storm-object centroid. storm_object_table.centroid_longitude_deg: Longitude (deg E) of storm-object centroid. storm_object_table.polygon_object_latlng_deg: Instance of `shapely.geometry.Polygon`, with x-coords in longitude (deg E) and y-coords in latitude (deg N). :param min_distances_metres: length-K numpy array of minimum distances. If the storm object is inside the [k]th buffer -- i.e., the [k]th buffer has no minimum distance -- then min_distances_metres[k] should be NaN. :param max_distances_metres: length-K numpy array of max distances. :return: storm_object_table: Same as input but with K additional columns (one per distance buffer). Column names are generated by `buffer_to_column_name`, and each value in these columns is a `shapely.geometry.Polygon` object, with x-coords in longitude (deg E) and y-coords in latitude (deg N). """ num_buffers = len(min_distances_metres) these_expected_dim = numpy.array([num_buffers], dtype=int) error_checking.assert_is_numpy_array(max_distances_metres, exact_dimensions=these_expected_dim) global_centroid_lat_deg, global_centroid_lng_deg = ( geodetic_utils.get_latlng_centroid( latitudes_deg=storm_object_table[CENTROID_LATITUDE_COLUMN].values, longitudes_deg=storm_object_table[CENTROID_LONGITUDE_COLUMN].values )) projection_object = projections.init_azimuthal_equidistant_projection( central_latitude_deg=global_centroid_lat_deg, central_longitude_deg=global_centroid_lng_deg) num_storm_objects = len(storm_object_table.index) object_array = numpy.full(num_storm_objects, numpy.nan, dtype=object) buffer_column_names = [''] * num_buffers for j in range(num_buffers): buffer_column_names[j] = buffer_to_column_name( min_distance_metres=min_distances_metres[j], max_distance_metres=max_distances_metres[j]) storm_object_table = storm_object_table.assign( **{buffer_column_names[j]: object_array}) for i in range(num_storm_objects): this_orig_vertex_dict_latlng_deg = ( polygons.polygon_object_to_vertex_arrays( storm_object_table[LATLNG_POLYGON_COLUMN].values[i])) these_orig_x_metres, these_orig_y_metres = ( projections.project_latlng_to_xy( latitudes_deg=this_orig_vertex_dict_latlng_deg[ polygons.EXTERIOR_Y_COLUMN], longitudes_deg=this_orig_vertex_dict_latlng_deg[ polygons.EXTERIOR_X_COLUMN], projection_object=projection_object)) for j in range(num_buffers): this_buffer_poly_object_xy_metres = polygons.buffer_simple_polygon( vertex_x_metres=these_orig_x_metres, vertex_y_metres=these_orig_y_metres, min_buffer_dist_metres=min_distances_metres[j], max_buffer_dist_metres=max_distances_metres[j]) this_buffer_vertex_dict = polygons.polygon_object_to_vertex_arrays( this_buffer_poly_object_xy_metres) (this_buffer_vertex_dict[polygons.EXTERIOR_Y_COLUMN], this_buffer_vertex_dict[polygons.EXTERIOR_X_COLUMN] ) = projections.project_xy_to_latlng( x_coords_metres=this_buffer_vertex_dict[ polygons.EXTERIOR_X_COLUMN], y_coords_metres=this_buffer_vertex_dict[ polygons.EXTERIOR_Y_COLUMN], projection_object=projection_object) this_num_holes = len( this_buffer_vertex_dict[polygons.HOLE_X_COLUMN]) for k in range(this_num_holes): (this_buffer_vertex_dict[polygons.HOLE_Y_COLUMN][k], this_buffer_vertex_dict[polygons.HOLE_X_COLUMN][k] ) = projections.project_xy_to_latlng( x_coords_metres=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN][k], y_coords_metres=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN][k], projection_object=projection_object) this_buffer_poly_object_latlng_deg = ( polygons.vertex_arrays_to_polygon_object( exterior_x_coords=this_buffer_vertex_dict[ polygons.EXTERIOR_X_COLUMN], exterior_y_coords=this_buffer_vertex_dict[ polygons.EXTERIOR_Y_COLUMN], hole_x_coords_list=this_buffer_vertex_dict[ polygons.HOLE_X_COLUMN], hole_y_coords_list=this_buffer_vertex_dict[ polygons.HOLE_Y_COLUMN])) storm_object_table[buffer_column_names[j]].values[i] = ( this_buffer_poly_object_latlng_deg) return storm_object_table
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