3737 get_RMW_regression_coefs ,
3838 RMW_bias_correction ,
3939 RMWFillMethod ,
40+ PcFillMethod ,
41+ OMEGA ,
42+ BETA_00 ,
43+ BETA_V20 ,
44+ BETA_fR ,
45+ BETA_fRdV ,
46+ BETA_01 ,
47+ BETA_V21 ,
4048)
4149from stormevents .utilities import subset_time_interval
4250
@@ -55,6 +63,7 @@ def __init__(
5563 advisories : List [ATCF_Advisory ] = None ,
5664 forecast_time : datetime = None ,
5765 rmw_fill : RMWFillMethod = RMWFillMethod .regression_penny_2023 ,
66+ pc_fill : PcFillMethod = PcFillMethod .persistent_holland_b ,
5867 ):
5968 """
6069 :param storm: storm ID, or storm name and year
@@ -110,6 +119,7 @@ def __init__(
110119 self .advisories = advisories
111120 self .file_deck = file_deck
112121 self .rmw_fill = rmw_fill
122+ self .pc_fill = pc_fill
113123
114124 self .__previous_configuration = self .__configuration
115125
@@ -129,6 +139,7 @@ def from_storm_name(
129139 advisories : List [ATCF_Advisory ] = None ,
130140 forecast_time : datetime = None ,
131141 rmw_fill : RMWFillMethod = RMWFillMethod .regression_penny_2023 ,
142+ pc_fill : PcFillMethod = PcFillMethod .persistent_holland_b ,
132143 ) -> "VortexTrack" :
133144 """
134145 :param name: storm name
@@ -153,6 +164,7 @@ def from_storm_name(
153164 advisories = advisories ,
154165 forecast_time = forecast_time ,
155166 rmw_fill = rmw_fill ,
167+ pc_fill = pc_fill ,
156168 )
157169
158170 @classmethod
@@ -165,6 +177,7 @@ def from_file(
165177 advisories : List [ATCF_Advisory ] = None ,
166178 forecast_time : datetime = None ,
167179 rmw_fill : RMWFillMethod = RMWFillMethod .regression_penny_2023 ,
180+ pc_fill : PcFillMethod = PcFillMethod .persistent_holland_b ,
168181 ) -> "VortexTrack" :
169182 """
170183 :param path: file path to ATCF data
@@ -197,6 +210,7 @@ def from_file(
197210 advisories = advisories ,
198211 forecast_time = forecast_time ,
199212 rmw_fill = rmw_fill ,
213+ pc_fill = pc_fill ,
200214 )
201215
202216 @property
@@ -499,7 +513,7 @@ def filename(self, filename: PathLike):
499513 @property
500514 def rmw_fill (self ) -> RMWFillMethod :
501515 """
502- :return: file path to read file (optional)
516+ :return: RMW filling method
503517 """
504518
505519 return self .__rmw_fill
@@ -511,6 +525,21 @@ def rmw_fill(self, rmw_fill: RMWFillMethod):
511525
512526 self .__rmw_fill = rmw_fill
513527
528+ @property
529+ def pc_fill (self ) -> PcFillMethod :
530+ """
531+ :return: Pc filling method
532+ """
533+
534+ return self .__pc_fill
535+
536+ @pc_fill .setter
537+ def pc_fill (self , pc_fill : PcFillMethod ):
538+ if pc_fill is None or not isinstance (pc_fill , PcFillMethod ):
539+ pc_fill = PcFillMethod .none
540+
541+ self .__pc_fill = pc_fill
542+
514543 @property
515544 def data (self ) -> DataFrame :
516545 """
@@ -1068,7 +1097,7 @@ def unfiltered_data(self, dataframe: DataFrame):
10681097 tracks = separate_tracks (dataframe )
10691098 if all (adv in tracks for adv in ["OFCL" , "CARQ" ]):
10701099 tracks = correct_ofcl_based_on_carq_n_hollandb (
1071- tracks , rmw_fill = self .rmw_fill
1100+ tracks , rmw_fill = self .rmw_fill , pc_fill = self . pc_fill
10721101 )
10731102 dataframe = combine_tracks (tracks )
10741103
@@ -1090,6 +1119,7 @@ def __configuration(self) -> Dict[str, Any]:
10901119 "advisories" : self .advisories ,
10911120 "filename" : self .filename ,
10921121 "rmw_fill" : self .rmw_fill ,
1122+ "pc_fill" : self .pc_fill ,
10931123 }
10941124
10951125 @staticmethod
@@ -1148,7 +1178,7 @@ def __compute_velocity(data: DataFrame) -> DataFrame:
11481178 bearings .ffill (inplace = True )
11491179 speeds .bfill (inplace = True )
11501180 bearings .bfill (inplace = True )
1151- advisory_data ["speed" ] = speeds
1181+ advisory_data ["speed" ] = speeds * 1.9438 # m/s to kt
11521182 advisory_data ["direction" ] = bearings
11531183
11541184 data .loc [data ["advisory" ] == advisory ] = advisory_data
@@ -1226,6 +1256,95 @@ def max_sustained_wind_speed(
12261256 )
12271257
12281258
1259+ def chavas_2025_Pc (data : DataFrame ):
1260+ """
1261+ perform the Chavas et al. (2025) regression method for filling in central pressure
1262+ Ref:
1263+ Chavas, D. R., Knaff, J. A., & Klotzbach, P. (2025).
1264+ A Simple Model for Predicting Tropical Cyclone Minimum Central Pressure from Intensity and Size.
1265+ Weather and Forecasting, 40, 333–346.
1266+ https://doi.org/10.1175/WAF-D-24-0031.1
1267+
1268+ :param data: data frame of track with missing entries
1269+ :return: central pressure values
1270+ """
1271+
1272+ fo2 = OMEGA * numpy .sin (numpy .deg2rad (data .latitude )) # half coriolis [1/s]
1273+ Vmax = 0.5144 * (
1274+ data .max_sustained_wind_speed - 0.55 * data .speed
1275+ ) # azimuthal mean Vmax [m/s]
1276+ isotach_radii = data [
1277+ [
1278+ "isotach_radius_for_NEQ" ,
1279+ "isotach_radius_for_SEQ" ,
1280+ "isotach_radius_for_NWQ" ,
1281+ "isotach_radius_for_SWQ" ,
1282+ ]
1283+ ]
1284+ # make any 0 value NaN
1285+ isotach_radii [isotach_radii == 0 ] = pandas .NA
1286+ R34 = 0.85 * isotach_radii .mean (axis = 1 ) * 1852 # average R34 radius [m]
1287+ # forward fill to fill in 50-kt and 64-kt rows with the R34 value as
1288+ R34 [data .isotach_radius > 35 ] = pandas .NA
1289+ R34 [data .isotach_radius > 35 ] = R34 .ffill ()[data .isotach_radius > 35 ]
1290+ # equation where R34 is available
1291+ deltaP = (
1292+ BETA_00
1293+ + BETA_V20 * Vmax * Vmax
1294+ + BETA_fR * fo2 * R34
1295+ + BETA_fRdV * fo2 * R34 / Vmax
1296+ ) # [hPa]
1297+ # equation where R34 isn't available
1298+ deltaP [deltaP .isna ()] = BETA_01 + BETA_V21 * Vmax * Vmax # [hPa]
1299+ return data .background_pressure + 2 + deltaP # Pc
1300+
1301+
1302+ def courtney_knaff_2009_Pc (data : DataFrame ):
1303+ """
1304+ perform the Courtney & Knaff (2009) regression method for filling in central pressure
1305+ Ref:
1306+ Courtney, J. & Knaff, J. (2009).
1307+ Adapting the Knaff and Zehr wind-pressure relationship for operational use in Tropical Cyclone Warning Centers.
1308+ Australian Meteorological and Oceanographic Journal, 58, 167-179.
1309+ https://connectsci.au/es/article/58/3/167/264593/Adapting-the-Knaff-and-Zehr-wind-pressure
1310+
1311+ :param data: data frame of track with missing entries
1312+ :return: central pressure values
1313+ """
1314+
1315+ Vmax = data .max_sustained_wind_speed # Vmax [kt]
1316+ Vsrm = Vmax - 1.5 * data .speed ** 0.63 # azimuthal mean Vmax [kt]
1317+ isotach_radii = data [
1318+ [
1319+ "isotach_radius_for_NEQ" ,
1320+ "isotach_radius_for_SEQ" ,
1321+ "isotach_radius_for_NWQ" ,
1322+ "isotach_radius_for_SWQ" ,
1323+ ]
1324+ ]
1325+ # make any 0 value NaN
1326+ isotach_radii [isotach_radii == 0 ] = pandas .NA
1327+ R34 = 0.85 * isotach_radii .mean (axis = 1 ) # average R34 radius [n mi]
1328+ # forward fill to fill in 50-kt and 64-kt rows with the R34 value as
1329+ R34 [data .isotach_radius > 35 ] = pandas .NA
1330+ R34 [data .isotach_radius > 35 ] = R34 .ffill ()[data .isotach_radius > 35 ]
1331+ V500 = R34 / 9 - 3 # wind speed at 500 km radius
1332+ lat = data .latitude # latitude [deg]
1333+ x = 0.1147 + 0.0055 * Vmax - 0.001 * (lat - 25 )
1334+ Rmax = 66.785 - 0.09102 * Vmax + 1.0619 * (lat - 25 )
1335+ V500c = Vmax * (Rmax / 500 ) ** x # climatological wind speed at 500 km radius
1336+ S = V500 / V500c # normalized storm size
1337+ S [(S < 0.4 ) | (S .isna ())] = 0.4 # lower bound/default value of 0.4
1338+ # equation for lat >= 18 deg
1339+ deltaP = (
1340+ 23.286 - 0.483 * Vsrm - (Vsrm / 24.524 ) ** 2 - 12.587 * S - 0.483 * lat
1341+ ) # [hPa]
1342+ # equation for lat < 18 deg
1343+ deltaP_lo = 5.962 - 0.267 * Vsrm - (Vsrm / 18.26 ) ** 2 - 6.8 * S # [hPa]
1344+ deltaP [lat < 18 ] = deltaP_lo [lat < 18 ]
1345+ return data .background_pressure + 2 + deltaP # Pc
1346+
1347+
12291348def separate_tracks (data : DataFrame ) -> Dict [str , Dict [str , DataFrame ]]:
12301349 """
12311350 separate the given track data frame into advisories and tracks (forecasts / hindcasts)
@@ -1275,6 +1394,7 @@ def combine_tracks(tracks: Dict[str, Dict[str, DataFrame]]) -> DataFrame:
12751394def correct_ofcl_based_on_carq_n_hollandb (
12761395 tracks : Dict [str , Dict [str , DataFrame ]],
12771396 rmw_fill : RMWFillMethod = RMWFillMethod .regression_penny_2023 ,
1397+ pc_fill : PcFillMethod = PcFillMethod .persistent_holland_b ,
12781398) -> Dict [str , Dict [str , DataFrame ]]:
12791399 """
12801400 Correct official forecast using consensus track along with holland-b
@@ -1347,19 +1467,19 @@ def movingmean(dff):
13471467 else :
13481468 carq_forecast = carq_tracks [list (carq_tracks )[0 ]]
13491469
1470+ # Get CARQ from forecast hour 0 and isotach 34kt (i.e. the first item)
1471+ carq_ref = carq_forecast .loc [carq_forecast .forecast_hours == 0 ].iloc [0 ]
1472+
1473+ # get the Holland B parameter for filling in central pressure
1474+ # with persistent Holland B option
13501475 relation = HollandBRelation ()
13511476 holland_b = relation .holland_b (
1352- max_sustained_wind_speed = carq_forecast ["max_sustained_wind_speed" ],
1353- background_pressure = carq_forecast ["background_pressure" ],
1354- central_pressure = carq_forecast ["central_pressure" ],
1477+ max_sustained_wind_speed = carq_ref ["max_sustained_wind_speed" ],
1478+ background_pressure = carq_ref ["background_pressure" ],
1479+ central_pressure = carq_ref ["central_pressure" ],
13551480 )
13561481
1357- holland_b [holland_b == numpy .inf ] = numpy .nan
1358- holland_b = numpy .nanmean (holland_b )
1359-
1360- # Get CARQ from forecast hour 0 and isotach 34kt (i.e. the first item)
1361- carq_ref = carq_forecast .loc [carq_forecast .forecast_hours == 0 ].iloc [0 ]
1362-
1482+ # find locations where the pertinent variables are missing
13631483 columns_of_interest = forecast [
13641484 ["radius_of_maximum_winds" , "central_pressure" , "background_pressure" ]
13651485 ]
@@ -1444,14 +1564,32 @@ def movingmean(dff):
14441564 "background_pressure"
14451565 ]
14461566
1447- # fill OFCL central pressure (at sea level), preserving Holland B from 0-hr CARQ
1448- forecast .loc [mslp_missing , "central_pressure" ] = relation .central_pressure (
1449- max_sustained_wind_speed = forecast .loc [
1450- mslp_missing , "max_sustained_wind_speed"
1451- ],
1452- background_pressure = forecast .loc [mslp_missing , "background_pressure" ],
1453- holland_b = holland_b ,
1454- )
1567+ # fill OFCL central pressure (at sea level):
1568+ if pc_fill == PcFillMethod .persistent_holland_b :
1569+ # preserving Holland B from 0-hr CARQ
1570+ relation = HollandBRelation ()
1571+ holland_b = relation .holland_b (
1572+ max_sustained_wind_speed = carq_ref ["max_sustained_wind_speed" ],
1573+ background_pressure = carq_ref ["background_pressure" ],
1574+ central_pressure = carq_ref ["central_pressure" ],
1575+ )
1576+ forecast .loc [mslp_missing , "central_pressure" ] = relation .central_pressure (
1577+ max_sustained_wind_speed = forecast .loc [
1578+ mslp_missing , "max_sustained_wind_speed"
1579+ ],
1580+ background_pressure = forecast .loc [mslp_missing , "background_pressure" ],
1581+ holland_b = holland_b ,
1582+ )
1583+ elif pc_fill == PcFillMethod .regression_chavas_2025 :
1584+ # use the Chavas et al. (2025) regression method
1585+ forecast .loc [mslp_missing , "central_pressure" ] = chavas_2025_Pc (
1586+ forecast .loc [mslp_missing , :]
1587+ )
1588+ elif pc_fill == PcFillMethod .regression_courtney_knaff_2009 :
1589+ # use the Courtney & Knaff (2009) regression method
1590+ forecast .loc [mslp_missing , "central_pressure" ] = courtney_knaff_2009_Pc (
1591+ forecast .loc [mslp_missing , :]
1592+ )
14551593
14561594 corr_ofcl_tracks [initial_time ] = forecast
14571595
0 commit comments