def test_estimate_params_pass(self): """Test track parameter estimation functions.""" cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) v_max = np.array([45, np.nan, 50, 55, 0, 60, 75]) lat = np.array([13.8, 13.9, 14, 14.1, 14.1, np.nan, -999]) lon = np.array([np.nan, -52.8, -54.4, -56, -58.4, -59.7, -61.1]) ref_pres = np.array([np.nan, 993, 990, 986, np.nan, 1004, np.nan]) out_pres = tc._estimate_pressure(cen_pres, lat, lon, v_max) np.testing.assert_array_almost_equal(ref_pres, out_pres, decimal=0) v_max = np.array([45, np.nan, 50, 55, 0, 60, 75]) cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) lat = np.array([13.8, 13.9, 14, 14.1, 14.1, np.nan, -999]) lon = np.array([np.nan, -52.8, -54.4, -56, -58.4, -59.7, -61.1]) ref_vmax = np.array([45, 46, 50, 55, np.nan, 60, 75]) out_vmax = tc._estimate_vmax(v_max, lat, lon, cen_pres) np.testing.assert_array_almost_equal(ref_vmax, out_vmax, decimal=0) roci = np.array([np.nan, -1, 145, 170, 180, 0, -5]) cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) ref_roci = np.array( [np.nan, 182.792715, 145, 170, 180, 161.5231086, np.nan]) out_roci = tc.estimate_roci(roci, cen_pres) np.testing.assert_array_almost_equal(ref_roci, out_roci) rmw = np.array([17, 33, -1, 25, np.nan, -5, 13]) cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) ref_rmw = np.array([17, 33, np.nan, 25, np.nan, 43.95543761, 13]) out_rmw = tc.estimate_rmw(rmw, cen_pres) np.testing.assert_array_almost_equal(ref_rmw, out_rmw)
def test_estimate_params_pass(self): """Test track parameter estimation functions.""" cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) v_max = np.array([45, np.nan, 50, 55, 0, 60, 75]) lat = np.array([13.8, 13.9, 14, 14.1, 14.1, np.nan, -999]) lon = np.array([np.nan, -52.8, -54.4, -56, -58.4, -59.7, -61.1]) ref_pres = np.array( [np.nan, 993, 990.2324, 986.6072, np.nan, 1004, np.nan]) out_pres = tc._estimate_pressure(cen_pres, lat, lon, v_max) self.assertTrue(np.allclose(ref_pres, out_pres, equal_nan=True)) v_max = np.array([45, np.nan, 50, 55, 0, 60, 75]) cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) lat = np.array([13.8, 13.9, 14, 14.1, 14.1, np.nan, -999]) lon = np.array([np.nan, -52.8, -54.4, -56, -58.4, -59.7, -61.1]) ref_vmax = np.array([45, 46.38272, 50, 55, np.nan, 60, 75]) out_vmax = tc._estimate_vmax(v_max, lat, lon, cen_pres) self.assertTrue(np.allclose(ref_vmax, out_vmax, equal_nan=True)) roci = np.array([np.nan, -1, 145, 170, 180, 0, -5]) cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) ref_roci = np.array( [np.nan, 182.792715, 145, 170, 180, 161.5231086, np.nan]) out_roci = tc.estimate_roci(roci, cen_pres) self.assertTrue(np.allclose(ref_roci, out_roci, equal_nan=True)) rmw = np.array([17, 33, -1, 25, np.nan, -5, 13]) cen_pres = np.array([-999, 993, np.nan, -1, 0, 1004, np.nan]) ref_rmw = np.array([17, 33, np.nan, 25, np.nan, 43.95543761, 13]) out_rmw = tc.estimate_rmw(rmw, cen_pres) self.assertTrue(np.allclose(ref_rmw, out_rmw, equal_nan=True))
def track_data_force_HRS(forcast_df, HRS_SPEED): track = xr.Dataset( data_vars={ 'max_sustained_wind': ('time', pd.Series(HRS_SPEED).interpolate().tolist()), 'environmental_pressure': ('time', forcast_df.environmental_pressure.values), 'central_pressure': ('time', pd.Series( forcast_df.central_pressure.values).interpolate().tolist()), 'lat': ('time', pd.Series(forcast_df.lat.values).interpolate().tolist()), 'lon': ('time', pd.Series(forcast_df.lon.values).interpolate().tolist()), 'radius_max_wind': ('time', pd.Series( forcast_df.radius_max_wind.values).interpolate().tolist()), 'radius_max_wind': ('time', estimate_rmw(forcast_df.radius_max_wind.values, forcast_df.central_pressure.values)), 'radius_oci': ('time', estimate_roci(forcast_df.radius_max_wind.values, forcast_df.central_pressure.values)), 'time_step': ('time', forcast_df.time_step.values), }, coords={ 'time': forcast_df.time.values, }, attrs={ 'max_sustained_wind_unit': 'm/s', 'central_pressure_unit': 'mb', 'name': forcast_df.name, 'sid': forcast_df.sid, #+str(forcast_df.ensemble_number), 'orig_event_flag': forcast_df.orig_event_flag, 'data_provider': forcast_df.data_provider, 'id_no': forcast_df.id_no, 'ensemble_number': forcast_df.ensemble_number, 'is_ensemble': forcast_df.is_ensemble, 'forecast_time': forcast_df.forecast_time, 'basin': forcast_df.basin, 'category': forcast_df.category, }) track = track.set_coords(['lat', 'lon']) return track
def test_estimate_rmw_pass(self): """Test estimate_rmw function.""" NM_TO_KM = (1.0 * ureg.nautical_mile).to(ureg.kilometer).magnitude tc_track = tc.TCTracks() tc_track.read_processed_ibtracs_csv(TEST_TRACK) tc_track.equal_timestep() rad_max_wind = tc.estimate_rmw( tc_track.data[0].radius_max_wind.values, tc_track.data[0].central_pressure.values) * NM_TO_KM self.assertAlmostEqual(rad_max_wind[0], 87, places=0) self.assertAlmostEqual(rad_max_wind[10], 87, places=0) self.assertAlmostEqual(rad_max_wind[128], 56, places=0) self.assertAlmostEqual(rad_max_wind[129], 55, places=0) self.assertAlmostEqual(rad_max_wind[130], 54, places=0) self.assertAlmostEqual(rad_max_wind[189], 53, places=0) self.assertAlmostEqual(rad_max_wind[190], 55, places=0) self.assertAlmostEqual(rad_max_wind[191], 57, places=0) self.assertAlmostEqual(rad_max_wind[192], 58, places=0) self.assertAlmostEqual(rad_max_wind[200], 71, places=0)
def test_estimate_rmw_pass(self): """Test estimate_rmw function.""" NM_TO_KM = (1.0 * ureg.nautical_mile).to(ureg.kilometer).magnitude tc_track = tc.TCTracks() tc_track.read_processed_ibtracs_csv(TEST_TRACK) tc_track.equal_timestep() rad_max_wind = tc.estimate_rmw( tc_track.data[0].radius_max_wind.values, tc_track.data[0].central_pressure.values) * NM_TO_KM self.assertAlmostEqual(rad_max_wind[0], 86.4471340900, places=5) self.assertAlmostEqual(rad_max_wind[10], 86.525605570, places=5) self.assertAlmostEqual(rad_max_wind[128], 55.25462781, places=5) self.assertAlmostEqual(rad_max_wind[129], 54.40164284, places=5) self.assertAlmostEqual(rad_max_wind[130], 53.54865787, places=5) self.assertAlmostEqual(rad_max_wind[189], 52.62700450, places=5) self.assertAlmostEqual(rad_max_wind[190], 54.36738477, places=5) self.assertAlmostEqual(rad_max_wind[191], 56.10776504, places=5) self.assertAlmostEqual(rad_max_wind[192], 57.84814530, places=5) self.assertAlmostEqual(rad_max_wind[200], 70.00942075, places=5)
def compute_windfields(track, centroids, model, metric="equirect"): """Compute 1-minute sustained winds (in m/s) at 10 meters above ground In a first step, centroids within reach of the track are determined so that wind fields will only be computed and returned for those centroids. Parameters ---------- track : xr.Dataset Track infomation. centroids : 2d np.array Each row is a centroid [lat, lon]. Centroids that are not within reach of the track are ignored. model : int Holland model selection according to MODEL_VANG. metric : str, optional Specify an approximation method to use for earth distances: "equirect" (faster) or "geosphere" (more accurate). See `dist_approx` function in `climada.util.coordinates`. Default: "equirect". Returns ------- windfields : np.array of shape (npositions, nreachable, 2) Directional wind fields for each track position on those centroids within reach of the TC track. reachable_centr_idx : np.array of shape (nreachable,) List of indices of input centroids within reach of the TC track. """ # copies of track data # Note that max wind records are not used in the Holland wind field models! t_lat, t_lon, t_tstep, t_rad, t_env, t_cen = [ track[ar].values.copy() for ar in [ 'lat', 'lon', 'time_step', 'radius_max_wind', 'environmental_pressure', 'central_pressure' ] ] # start with the assumption that no centroids are within reach npositions = t_lat.shape[0] reachable_centr_idx = np.zeros((0, ), dtype=np.int64) windfields = np.zeros((npositions, 0, 2), dtype=np.float64) # the wind field model requires at least two track positions because translational speed # as well as the change in pressure are required if npositions < 2: return windfields, reachable_centr_idx # normalize longitude values (improves performance of `dist_approx` and `_close_centroids`) mid_lon = 0.5 * sum(u_coord.lon_bounds(t_lon)) u_coord.lon_normalize(t_lon, center=mid_lon) u_coord.lon_normalize(centroids[:, 1], center=mid_lon) # restrict to centroids within rectangular bounding boxes around track positions track_centr_msk = _close_centroids(t_lat, t_lon, centroids) track_centr = centroids[track_centr_msk] nreachable = track_centr.shape[0] if nreachable == 0: return windfields, reachable_centr_idx # compute distances and vectors to all centroids [d_centr], [v_centr] = u_coord.dist_approx(t_lat[None], t_lon[None], track_centr[None, :, 0], track_centr[None, :, 1], log=True, normalize=False, method=metric) # exclude centroids that are too far from or too close to the eye close_centr_msk = (d_centr < CENTR_NODE_MAX_DIST_KM) & (d_centr > 1e-2) if not np.any(close_centr_msk): return windfields, reachable_centr_idx v_centr_normed = np.zeros_like(v_centr) v_centr_normed[close_centr_msk] = v_centr[close_centr_msk] / d_centr[ close_centr_msk, None] # make sure that central pressure never exceeds environmental pressure pres_exceed_msk = (t_cen > t_env) t_cen[pres_exceed_msk] = t_env[pres_exceed_msk] # extrapolate radius of max wind from pressure if not given t_rad[:] = estimate_rmw(t_rad, t_cen) * NM_TO_KM # translational speed of track at every node v_trans = _vtrans(t_lat, t_lon, t_tstep, metric=metric) v_trans_norm = v_trans[0] # adjust pressure at previous track point prev_pres = t_cen[:-1].copy() msk = (prev_pres < 850) prev_pres[msk] = t_cen[1:][msk] # compute b-value and derive (absolute) angular velocity if model == MODEL_VANG['H1980']: # convert recorded surface winds to gradient-level winds without translational influence t_vmax = track.max_sustained_wind.values.copy() t_gradient_winds = np.fmax( 0, t_vmax - v_trans_norm) / GRADIENT_LEVEL_TO_SURFACE_WINDS hol_b = _B_holland_1980(t_gradient_winds[1:], t_env[1:], t_cen[1:]) v_ang_norm = _stat_holland_1980(d_centr[1:], t_rad[1:], hol_b, t_env[1:], t_cen[1:], t_lat[1:], close_centr_msk[1:]) v_ang_norm *= GRADIENT_LEVEL_TO_SURFACE_WINDS elif model == MODEL_VANG['H08']: # this model doesn't use the recorded surface winds hol_b = _bs_holland_2008(v_trans_norm[1:], t_env[1:], t_cen[1:], prev_pres, t_lat[1:], t_tstep[1:]) v_ang_norm = _stat_holland_1980(d_centr[1:], t_rad[1:], hol_b, t_env[1:], t_cen[1:], t_lat[1:], close_centr_msk[1:]) elif model == MODEL_VANG['H10']: # this model doesn't use the recorded surface winds hol_b = _bs_holland_2008(v_trans_norm[1:], t_env[1:], t_cen[1:], prev_pres, t_lat[1:], t_tstep[1:]) t_vmax = _v_max_s_holland_2008(t_env[1:], t_cen[1:], hol_b) hol_x = _x_holland_2010(d_centr[1:], t_rad[1:], t_vmax, hol_b, close_centr_msk[1:]) v_ang_norm = _stat_holland_2010(d_centr[1:], t_vmax, t_rad[1:], hol_b, close_centr_msk[1:], hol_x) else: raise NotImplementedError # vectorial angular velocity hemisphere = 'N' if np.count_nonzero(t_lat < 0) > np.count_nonzero(t_lat > 0): hemisphere = 'S' v_ang_rotate = [1.0, -1.0] if hemisphere == 'N' else [-1.0, 1.0] v_ang_dir = np.array(v_ang_rotate)[..., :] * v_centr_normed[1:, :, ::-1] v_ang = np.zeros_like(v_ang_dir) v_ang[close_centr_msk[1:]] = v_ang_norm[close_centr_msk[1:], None] \ * v_ang_dir[close_centr_msk[1:]] # Influence of translational speed decreases with distance from eye. # The "absorbing factor" is according to the following paper (see Fig. 7): # # Mouton, F., & Nordbeck, O. (1999). Cyclone Database Manager. A tool # for converting point data from cyclone observations into tracks and # wind speed profiles in a GIS. UNED/GRID-Geneva. # https://unepgrid.ch/en/resource/19B7D302 # t_rad_bc = np.broadcast_arrays(t_rad[:, None], d_centr)[0] v_trans_corr = np.zeros_like(d_centr) v_trans_corr[close_centr_msk] = np.fmin( 1, t_rad_bc[close_centr_msk] / d_centr[close_centr_msk]) # add angular and corrected translational velocity vectors v_full = v_trans[1][1:, None, :] * v_trans_corr[1:, :, None] + v_ang v_full[np.isnan(v_full)] = 0 windfields = np.zeros((npositions, nreachable, 2), dtype=np.float64) windfields[1:, :, :] = v_full [reachable_centr_idx] = track_centr_msk.nonzero() return windfields, reachable_centr_idx
def compute_windfields(track, centroids, model): """Compute 1-minute sustained winds (in m/s) at 10 meters above ground Parameters: track (xr.Dataset): track infomation centroids (2d np.array): each row is a centroid [lat, lon] model (int): Holland model selection according to MODEL_VANG Returns: np.array """ # copies of track data t_lat, t_lon, t_tstep, t_rad, t_env, t_cen = [ track[ar].values.copy() for ar in [ 'lat', 'lon', 'time_step', 'radius_max_wind', 'environmental_pressure', 'central_pressure' ] ] ncentroids = centroids.shape[0] npositions = t_lat.shape[0] windfields = np.zeros((npositions, ncentroids, 2)) if t_lon.size < 2: return windfields # never use longitudes at -180 degrees or below t_lon[t_lon <= -180] += 360 # only use longitudes above 180, if 180 degree border is crossed if t_lon.min() > 180: t_lon -= 360 # restrict to centroids in rectangular bounding box around track track_centr_msk = _close_centroids(t_lat, t_lon, centroids) track_centr_idx = track_centr_msk.nonzero()[0] track_centr = centroids[track_centr_msk] if track_centr.shape[0] == 0: return windfields # compute distances and vectors to all centroids d_centr, v_centr = [ ar[0] for ar in dist_approx(t_lat[None], t_lon[None], track_centr[None, :, 0], track_centr[None, :, 1], log=True, method="geosphere") ] # exclude centroids that are too far from or too close to the eye close_centr = (d_centr < CENTR_NODE_MAX_DIST_KM) & (d_centr > 1e-2) if not np.any(close_centr): return windfields v_centr_normed = np.zeros_like(v_centr) v_centr_normed[close_centr] = v_centr[close_centr] / d_centr[close_centr, None] # make sure that central pressure never exceeds environmental pressure pres_exceed_msk = (t_cen > t_env) t_cen[pres_exceed_msk] = t_env[pres_exceed_msk] # extrapolate radius of max wind from pressure if not given t_rad[:] = estimate_rmw(t_rad, t_cen) * NM_TO_KM # translational speed of track at every node v_trans = _vtrans(t_lat, t_lon, t_tstep) v_trans_norm = v_trans[0] # adjust pressure at previous track point prev_pres = t_cen[:-1].copy() msk = (prev_pres < 850) prev_pres[msk] = t_cen[1:][msk] # compute b-value if model == 0: hol_b = _bs_hol08(v_trans_norm[1:], t_env[1:], t_cen[1:], prev_pres, t_lat[1:], t_tstep[1:]) else: raise NotImplementedError # derive angular velocity v_ang_norm = _stat_holland(d_centr[1:], t_rad[1:], hol_b, t_env[1:], t_cen[1:], t_lat[1:], close_centr[1:]) hemisphere = 'N' if np.count_nonzero(t_lat < 0) > np.count_nonzero(t_lat > 0): hemisphere = 'S' v_ang_rotate = [1.0, -1.0] if hemisphere == 'N' else [-1.0, 1.0] v_ang_dir = np.array(v_ang_rotate)[..., :] * v_centr_normed[1:, :, ::-1] v_ang = np.zeros_like(v_ang_dir) v_ang[close_centr[1:]] = v_ang_norm[close_centr[1:], None] \ * v_ang_dir[close_centr[1:]] # Influence of translational speed decreases with distance from eye. # The "absorbing factor" is according to the following paper (see Fig. 7): # # Mouton, F., & Nordbeck, O. (1999). Cyclone Database Manager. A tool # for converting point data from cyclone observations into tracks and # wind speed profiles in a GIS. UNED/GRID-Geneva. # https://unepgrid.ch/en/resource/19B7D302 # t_rad_bc = np.broadcast_arrays(t_rad[:, None], d_centr)[0] v_trans_corr = np.zeros_like(d_centr) v_trans_corr[close_centr] = np.fmin( 1, t_rad_bc[close_centr] / d_centr[close_centr]) # add angular and corrected translational velocity vectors v_full = v_trans[1][1:, None, :] * v_trans_corr[1:, :, None] + v_ang v_full[np.isnan(v_full)] = 0 windfields[1:, track_centr_idx, :] = v_full return windfields