Skip to content

Commit ad6e2c2

Browse files
remove grid area func and normalization, reorganize
1 parent 34f5b50 commit ad6e2c2

File tree

3 files changed

+166
-347
lines changed

3 files changed

+166
-347
lines changed

climada/hazard/tc_tracks.py

Lines changed: 158 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,164 @@ def _one_interp_data(track, time_step_h, land_geom=None):
20892089
track_land_params(track_int, land_geom)
20902090
return track_int
20912091

2092+
def compute_track_density(
2093+
self,
2094+
res: int = 5,
2095+
bounds: tuple = None,
2096+
genesis: bool = False,
2097+
filter_tracks: bool = True,
2098+
wind_min: float = None,
2099+
wind_max: float = None,
2100+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
2101+
"""Compute tropical cyclone track density. Before using this function,
2102+
apply the same temporal resolution to all tracks by calling :py:meth:`equal_timestep` on the
2103+
TCTrack object. Due to the computational cost of the this function, it is not recommended to
2104+
use a grid resolution higher tha 0.1°. First, this function creates 2D bins of the specified
2105+
resolution (e.g. 1° x 1°). Second, since tracks are not lines but a series of points, it counts
2106+
the number of points per bin. Lastly, it returns the absolute count per bin.
2107+
To plot the output of this function use :py:meth:`plot_track_density`.
2108+
2109+
Parameters:
2110+
----------
2111+
tc_track: TCTracks object
2112+
track object containing a list of all tracks
2113+
res: int (optional), default: 5°
2114+
resolution in degrees of the grid bins in which the density will be computed
2115+
bounds: tuple, (optional) dafault: None
2116+
(lon_min, lat_min, lon_max, lat_max) latitude and longitude bounds.
2117+
genesis: bool, (optional) default = False
2118+
If true the function computes the track density of only the genesis location of tracks
2119+
filter_tracks: bool (optional) default: True
2120+
If True the track density is computed as the number of different tracks crossing a grid
2121+
cell. If False, the track density takes into account how long the track stayed in each
2122+
grid cell. Hence slower tracks increase the density if the parameter is set to False.
2123+
wind_min: float (optional), default: None
2124+
Minimum wind speed above which to select tracks (inclusive).
2125+
wind_max: float (optional), default: None
2126+
Maximal wind speed below which to select tracks (exclusive if wind_min is also provided,
2127+
otherwise inclusive).
2128+
Returns:
2129+
-------
2130+
hist_count: np.ndarray
2131+
2D matrix containing the the absolute count per grid cell of track point or the normalized
2132+
number of track points, depending on the norm parameter.
2133+
lat_bins: np.ndarray
2134+
latitude bins in which the point were counted
2135+
lon_bins: np.ndarray
2136+
longitude bins in which the point were counted
2137+
2138+
Example:
2139+
--------
2140+
>>> tc_tracks = TCTrack.from_ibtracs_netcdf("path_to_file")
2141+
>>> tc_tracks.equal_timestep(time_step_h = 1)
2142+
>>> hist_count, _, _ = compute_track_density(tc_track = tc_tracks, res = 1)
2143+
>>> ax = plot_track_density(hist_count)
2144+
2145+
"""
2146+
2147+
limit_ratio: float = (
2148+
1.12 * 1.1
2149+
) # record tc speed 112km/h -> 1.12°/h + 10% margin
2150+
time_value: float = self.data[0].time_step[0].values.astype(float)
2151+
2152+
if time_value > (res / limit_ratio):
2153+
warnings.warn(
2154+
"The time step is too big for the current resolution. For the desired resolution, \n"
2155+
f"apply a time step of {res/limit_ratio}h."
2156+
)
2157+
elif res < 0.1:
2158+
warnings.warn(
2159+
"The resolution is too high. The computation might take several minutes \n"
2160+
"to hours. Consider using a resolution below 0.1°."
2161+
)
2162+
2163+
# define grid resolution and bounds for density computation
2164+
if not bounds:
2165+
lon_min, lat_min, lon_max, lat_max = -180, -90, 180, 90
2166+
else:
2167+
lon_min, lat_min, lon_max, lat_max = (
2168+
bounds[0],
2169+
bounds[1],
2170+
bounds[2],
2171+
bounds[3],
2172+
)
2173+
2174+
lat_bins: np.ndarray = np.linspace(lat_min, lat_max, int(180 / res))
2175+
lon_bins: np.ndarray = np.linspace(lon_min, lon_max, int(360 / res))
2176+
2177+
# compute 2D density
2178+
if genesis:
2179+
hist_count = _compute_genesis_density(
2180+
tc_track=self, lat_bins=lat_bins, lon_bins=lon_bins
2181+
)
2182+
else:
2183+
hist_count = np.zeros((len(lat_bins) - 1, len(lon_bins) - 1))
2184+
for track in tqdm(self.data, desc="Processing Tracks"):
2185+
2186+
# select according to wind speed
2187+
wind_speed = track.max_sustained_wind.values
2188+
if wind_min and wind_max:
2189+
index = np.where(
2190+
(wind_speed >= wind_min) & (wind_speed < wind_max)
2191+
)[0]
2192+
elif wind_min and not wind_max:
2193+
index = np.where(wind_speed >= wind_min)[0]
2194+
elif wind_max and not wind_min:
2195+
index = np.where(wind_speed <= wind_max)[0]
2196+
else:
2197+
index = slice(None) # select all the track
2198+
2199+
# compute 2D density
2200+
hist_new, _, _ = np.histogram2d(
2201+
track.lat.values[index],
2202+
track.lon.values[index],
2203+
bins=[lat_bins, lon_bins],
2204+
density=False,
2205+
)
2206+
if filter_tracks:
2207+
hist_new[hist_new > 1] = 1
2208+
2209+
hist_count += hist_new
2210+
2211+
return hist_count, lat_bins, lon_bins
2212+
2213+
2214+
def _compute_genesis_density(
2215+
tc_track: TCTracks, lat_bins: np.ndarray, lon_bins: np.ndarray
2216+
) -> np.ndarray:
2217+
"""Compute the density of track genesis locations. This function works under the hood
2218+
of :py:meth:`compute_track_density`. If it is called with the parameter genesis = True,
2219+
the function return the number of genesis points per grid cell.
2220+
2221+
Parameters:
2222+
-----------
2223+
2224+
tc_track: TCT track object
2225+
TC track object containing a list of all tracks.
2226+
lat_bins: 1D np.array
2227+
array containg the latitude bins.
2228+
lon_bins: 1D np.array
2229+
array containg the longitude bins.
2230+
2231+
Returns:
2232+
--------
2233+
hist_count: 2D np.array
2234+
array containing the number of genesis points per grid cell
2235+
"""
2236+
# Extract the first lat and lon from each dataset
2237+
first_lats = np.array([ds.lat.values[0] for ds in tc_track.data])
2238+
first_lons = np.array([ds.lon.values[0] for ds in tc_track.data])
2239+
2240+
# compute 2D density of genesis points
2241+
hist_count, _, _ = np.histogram2d(
2242+
first_lats,
2243+
first_lons,
2244+
bins=[lat_bins, lon_bins],
2245+
density=False,
2246+
)
2247+
2248+
return hist_count
2249+
20922250

20932251
def _raise_if_legacy_or_unknown_hdf5_format(file_name):
20942252
"""Raise an exception if the HDF5 format of the file is not supported
@@ -3094,196 +3252,3 @@ def _zlib_from_dataarray(data_var: xr.DataArray) -> bool:
30943252
if np.issubdtype(data_var.dtype, float) or np.issubdtype(data_var.dtype, int):
30953253
return True
30963254
return False
3097-
3098-
3099-
def compute_track_density(
3100-
tc_track: TCTracks,
3101-
res: int = 5,
3102-
bounds: tuple = None,
3103-
genesis: bool = False,
3104-
norm: str = None,
3105-
filter_tracks: bool = True,
3106-
wind_min: float = None,
3107-
wind_max: float = None,
3108-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
3109-
"""Compute absolute and normalized tropical cyclone track density. Before using this function,
3110-
apply the same temporal resolution to all tracks by calling :py:meth:`equal_timestep` on the
3111-
TCTrack object. Due to the computational cost of the this function, it is not recommended to
3112-
use a grid resolution higher tha 0.1°. First, this function creates 2D bins of the specified
3113-
resolution (e.g. 1° x 1°). Second, since tracks are not lines but a series of points, it counts
3114-
the number of points per bin. Lastly, it returns the absolute or normalized count per bin.
3115-
To plot the output of this function use :py:meth:`plot_track_density`.
3116-
3117-
Parameters:
3118-
----------
3119-
tc_track: TCTracks object
3120-
track object containing a list of all tracks
3121-
res: int (optional), default: 5°
3122-
resolution in degrees of the grid bins in which the density will be computed
3123-
bounds: tuple, (optional) dafault: None
3124-
(lon_min, lat_min, lon_max, lat_max) latitude and longitude bounds.
3125-
genesis: bool, (optional) default = False
3126-
If true the function computes the track density of only the genesis location of tracks
3127-
norm: str (optional), default: None
3128-
If None the function returns the number of samples in each bin. If True, it normalize the
3129-
bin count as specified: if norm = area -> normalize by gird cell area. If norm = sum ->
3130-
normalize by the total sum across all bins.
3131-
filter_tracks: bool (optional) default: True
3132-
If True the track density is computed as the number of different tracks crossing a grid
3133-
cell. If False, the track density takes into account how long the track stayed in each
3134-
grid cell. Hence slower tracks increase the density if the parameter is set to False.
3135-
wind_min: float (optional), default: None
3136-
Minimum wind speed above which to select tracks (inclusive).
3137-
wind_max: float (optional), default: None
3138-
Maximal wind speed below which to select tracks (exclusive if wind_min is also provided,
3139-
otherwise inclusive).
3140-
Returns:
3141-
-------
3142-
hist_count: np.ndarray
3143-
2D matrix containing the the absolute count per grid cell of track point or the normalized
3144-
number of track points, depending on the norm parameter.
3145-
lat_bins: np.ndarray
3146-
latitude bins in which the point were counted
3147-
lon_bins: np.ndarray
3148-
longitude bins in which the point were counted
3149-
3150-
Example:
3151-
--------
3152-
>>> tc_tracks = TCTrack.from_ibtracs_netcdf("path_to_file")
3153-
>>> tc_tracks.equal_timestep(time_step_h = 1)
3154-
>>> hist_count, *_ = compute_track_density(tc_track = tc_tracks, res = 1)
3155-
>>> ax = plot_track_density(hist_count)
3156-
3157-
"""
3158-
3159-
limit_ratio: float = 1.12 * 1.1 # record tc speed 112km/h -> 1.12°/h + 10% margin
3160-
time_value: float = tc_track.data[0].time_step[0].values.astype(float)
3161-
3162-
if time_value > (res / limit_ratio):
3163-
warnings.warn(
3164-
"The time step is too big for the current resolution. For the desired resolution, \n"
3165-
f"apply a time step of {res/limit_ratio}h."
3166-
)
3167-
elif res < 0.1:
3168-
warnings.warn(
3169-
"The resolution is too high. The computation might take several minutes \n"
3170-
"to hours. Consider using a resolution below 0.1°."
3171-
)
3172-
3173-
# define grid resolution and bounds for density computation
3174-
if not bounds:
3175-
lon_min, lat_min, lon_max, lat_max = -180, -90, 180, 90
3176-
else:
3177-
lon_min, lat_min, lon_max, lat_max = bounds[0], bounds[1], bounds[2], bounds[3]
3178-
3179-
lat_bins: np.ndarray = np.linspace(lat_min, lat_max, int(180 / res))
3180-
lon_bins: np.ndarray = np.linspace(lon_min, lon_max, int(360 / res))
3181-
3182-
# compute 2D density
3183-
if genesis:
3184-
hist_count = compute_genesis_density(
3185-
tc_track=tc_track, lat_bins=lat_bins, lon_bins=lon_bins
3186-
)
3187-
else:
3188-
hist_count = np.zeros((len(lat_bins) - 1, len(lon_bins) - 1))
3189-
for track in tqdm(tc_track.data, desc="Processing Tracks"):
3190-
3191-
# select according to wind speed
3192-
wind_speed = track.max_sustained_wind.values
3193-
if wind_min and wind_max:
3194-
index = np.where((wind_speed >= wind_min) & (wind_speed < wind_max))[0]
3195-
elif wind_min and not wind_max:
3196-
index = np.where(wind_speed >= wind_min)[0]
3197-
elif wind_max and not wind_min:
3198-
index = np.where(wind_speed <= wind_max)[0]
3199-
else:
3200-
index = slice(None) # select all the track
3201-
3202-
# compute 2D density
3203-
hist_new, _, _ = np.histogram2d(
3204-
track.lat.values[index],
3205-
track.lon.values[index],
3206-
bins=[lat_bins, lon_bins],
3207-
density=False,
3208-
)
3209-
if filter_tracks:
3210-
hist_new[hist_new > 1] = 1
3211-
3212-
hist_count += hist_new
3213-
3214-
if norm:
3215-
hist_count = normalize_hist(res=res, hist_count=hist_count, norm=norm)
3216-
3217-
return hist_count, lat_bins, lon_bins
3218-
3219-
3220-
def compute_genesis_density(
3221-
tc_track: TCTracks, lat_bins: np.ndarray, lon_bins: np.ndarray
3222-
) -> np.ndarray:
3223-
"""Compute the density of track genesis locations. This function works under the hood
3224-
of :py:meth:`compute_track_density`. If it is called with the parameter genesis = True,
3225-
the function return the number of genesis points per grid cell.
3226-
3227-
Parameters:
3228-
-----------
3229-
3230-
tc_track: TCT track object
3231-
TC track object containing a list of all tracks.
3232-
lat_bins: 1D np.array
3233-
array containg the latitude bins.
3234-
lon_bins: 1D np.array
3235-
array containg the longitude bins.
3236-
3237-
Returns:
3238-
--------
3239-
hist_count: 2D np.array
3240-
array containing the number of genesis points per grid cell
3241-
"""
3242-
# Extract the first lat and lon from each dataset
3243-
first_lats = np.array([ds.lat.values[0] for ds in tc_track.data])
3244-
first_lons = np.array([ds.lon.values[0] for ds in tc_track.data])
3245-
3246-
# compute 2D density of genesis points
3247-
hist_count, _, _ = np.histogram2d(
3248-
first_lats,
3249-
first_lons,
3250-
bins=[lat_bins, lon_bins],
3251-
density=False,
3252-
)
3253-
3254-
return hist_count
3255-
3256-
3257-
def normalize_hist(
3258-
res: int,
3259-
hist_count: np.ndarray,
3260-
norm: str,
3261-
) -> np.ndarray:
3262-
"""Normalize the number of points per grid cell by the grid cell area or by the total sum of
3263-
the histogram count.
3264-
3265-
Parameters:
3266-
-----------
3267-
res: float
3268-
resolution of grid cells
3269-
hist_count: 2D np.array
3270-
Array containing the count of tracks or genesis locations
3271-
norm: str
3272-
if norm = area normalize by gird cell area, if norm = sum normalize by the total sum
3273-
Returns:
3274-
---------
3275-
3276-
"""
3277-
3278-
if norm == "area":
3279-
grid_area = u_coord.compute_grid_cell_area(res=res)
3280-
norm_hist: np.ndarray = hist_count / grid_area
3281-
elif norm == "sum":
3282-
norm_hist: np.ndarray = hist_count / hist_count.sum()
3283-
else:
3284-
raise ValueError(
3285-
"Invalid value for input parameter 'norm':\n"
3286-
"it should be either 'area' or 'sum'"
3287-
)
3288-
3289-
return norm_hist

0 commit comments

Comments
 (0)