Skip to content

Commit cadf062

Browse files
convert data cls to enum cls
1 parent 73889b9 commit cadf062

File tree

1 file changed

+97
-114
lines changed

1 file changed

+97
-114
lines changed

climada/hazard/tc_tracks.py

Lines changed: 97 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@
2929
import shutil
3030
import warnings
3131
from collections import defaultdict
32-
from dataclasses import dataclass
33-
from operator import itemgetter
32+
from enum import Enum
3433
from pathlib import Path
35-
from typing import Dict, List, Optional
34+
from typing import List, Optional
3635

3736
# additional libraries
3837
import cartopy.crs as ccrs
@@ -53,6 +52,7 @@
5352
from matplotlib.colors import BoundaryNorm, ListedColormap
5453
from matplotlib.lines import Line2D
5554
from shapely.geometry import LineString, MultiLineString, Point, Polygon
55+
from shapely.ops import unary_union
5656
from sklearn.metrics import DistanceMetric
5757

5858
import climada.hazard.tc_tracks_synth
@@ -195,15 +195,13 @@
195195
dataset using STORM. Scientific Data 7(1): 40."""
196196

197197

198-
@dataclass
199-
class Basin:
198+
class Basin(Enum):
200199
"""
201200
Store tropical cyclones basin geographical extent.
202-
203201
The boundaries of the basin are represented as a polygon (using the `shapely` Polygon object)
204-
and follows the definition of the STORM dataset.
205-
This class allows checking if a given geographical point (latitude, longitude)
206-
lies inside the basin (e.g. the orgin location of a track)
202+
and follows the definition of the STORM dataset. Important note: tropical cyclone boundaries
203+
may vary bewteen datasets. The following boundaries follows the STORM definition:
204+
https://www.nature.com/articles/s41597-020-0381-2
207205
208206
Attributes:
209207
----------
@@ -212,107 +210,80 @@ class Basin:
212210
*polygon : Polygon
213211
A shapely Polygon object that represents the geographical boundary of the basin.
214212
215-
Methods:
216-
-------
217-
contains()
218-
Returns `True` if the given point (latitude and longitude) is inside the basin's boundary,
219-
otherwise returns `False`.
220213
"""
221214

222-
name: str
223-
polygon: Polygon
215+
NA = Polygon(
216+
[
217+
(-100, 19),
218+
(-94.21951983987083, 17.039584804350312),
219+
(-88.75211790888072, 14.837521327451947),
220+
(-84.96610530622198, 12.214318798718033),
221+
(-84.89823142225451, 12.181148019885352),
222+
(-82.59052306410497, 8.777858931465238),
223+
(-81.09730008320902, 8.358383265470449),
224+
(-79.50226644452471, 9.196860922133856),
225+
(-78.58597052442947, 9.213610839871123),
226+
(-77.02487377167459, 7.299350879751048),
227+
(-77.02487377167459, 5),
228+
(0.0, 5.0),
229+
(0.0, 60.0),
230+
(-100.0, 60.0),
231+
(-100, 19),
232+
]
233+
)
224234

225-
def contains(self, point: Point) -> bool:
226-
"""
227-
Checks if a given point is inside the basin.
235+
EP = Polygon(
236+
[
237+
(-180.0, 5.0),
238+
(-77.02487377167459, 5),
239+
(-77.02487377167459, 7.299350879751048),
240+
(-78.58597052442947, 9.213610839871123),
241+
(-79.50226644452471, 9.196860922133856),
242+
(-81.09730008320902, 8.358383265470449),
243+
(-82.59052306410497, 8.777858931465238),
244+
(-84.89823142225451, 12.181148019885352),
245+
(-84.96610530622198, 12.214318798718033),
246+
(-88.75211790888072, 14.837521327451947),
247+
(-94.21951983987083, 17.039584804350312),
248+
(-100, 19),
249+
(-100.0, 60.0),
250+
(-180.0, 60.0),
251+
(-180.0, 5.0),
252+
]
253+
)
228254

229-
Parameters
230-
----------
231-
point : Point
232-
A shapely Point object representing the geographical location (longitude, latitude).
255+
WP = Polygon(
256+
[(100.0, 5.0), (180.0, 5.0), (180.0, 60.0), (100.0, 60.0), (100.0, 5.0)]
257+
)
233258

234-
Returns
235-
-------
236-
bool
237-
`True` if the point is inside the basin, `False` otherwise.
238-
"""
239-
return self.polygon.contains(point)
240-
241-
242-
BASINS: Dict[str, Basin] = {
243-
"NA": Basin(
244-
"NA",
245-
Polygon(
246-
[
247-
(-100, 19),
248-
(-94.21951983987083, 17.039584804350312),
249-
(-88.75211790888072, 14.837521327451947),
250-
(-84.96610530622198, 12.214318798718033),
251-
(-84.89823142225451, 12.181148019885352),
252-
(-82.59052306410497, 8.777858931465238),
253-
(-81.09730008320902, 8.358383265470449),
254-
(-79.50226644452471, 9.196860922133856),
255-
(-78.58597052442947, 9.213610839871123),
256-
(-77.02487377167459, 7.299350879751048),
257-
(-77.02487377167459, 5),
258-
(0, 5.0),
259-
(0, 60.0),
260-
(-100.0, 60.0),
261-
(-100, 19),
262-
]
263-
),
264-
),
265-
"EP": Basin(
266-
"EP",
267-
Polygon(
268-
[
269-
(-180.0, 5.0),
270-
(-77.02487377167459, 5),
271-
(-77.02487377167459, 7.299350879751048),
272-
(-78.58597052442947, 9.213610839871123),
273-
(-79.50226644452471, 9.196860922133856),
274-
(-81.09730008320902, 8.358383265470449),
275-
(-82.59052306410497, 8.777858931465238),
276-
(-84.89823142225451, 12.181148019885352),
277-
(-84.96610530622198, 12.214318798718033),
278-
(-88.75211790888072, 14.837521327451947),
279-
(-94.21951983987083, 17.039584804350312),
280-
(-100, 19),
281-
(-100.0, 60.0),
282-
(-180.0, 60.0),
283-
(-180.0, 5.0),
284-
]
285-
),
286-
),
287-
"WP": Basin(
288-
"WP",
289-
Polygon(
290-
[(100.0, 5.0), (180.0, 5.0), (180.0, 60.0), (100.0, 60.0), (100.0, 5.0)]
291-
),
292-
),
293-
"NI": Basin(
294-
"NI",
295-
Polygon([(30.0, 5.0), (100.0, 5.0), (100.0, 60.0), (30.0, 60.0), (30.0, 5.0)]),
296-
),
297-
"SI": Basin(
298-
"SI",
299-
Polygon(
300-
[(10.0, -60.0), (135.0, -60.0), (135.0, -5.0), (10.0, -5.0), (10.0, -60.0)]
301-
),
302-
),
303-
"SP": Basin(
304-
"SP",
305-
Polygon(
306-
[
307-
(135.0, -60.0),
308-
(240.0, -60.0),
309-
(240.0, -5.0),
310-
(135.0, -5.0),
311-
(135.0, -60.0),
312-
]
313-
),
314-
),
315-
}
259+
NI = Polygon([(30.0, 5.0), (100.0, 5.0), (100.0, 60.0), (30.0, 60.0), (30.0, 5.0)])
260+
261+
SI = Polygon(
262+
[(10.0, -60.0), (135.0, -60.0), (135.0, -5.0), (10.0, -5.0), (10.0, -60.0)]
263+
)
264+
265+
SP = unary_union(
266+
[
267+
Polygon( # west side of antimeridian
268+
[
269+
(135.0, -60.0),
270+
(180.0, -60.0),
271+
(180.0, -5.0),
272+
(135.0, -5.0),
273+
(135.0, -60.0),
274+
]
275+
),
276+
Polygon( # east side
277+
[
278+
(-180.0, -60.0),
279+
(-120.0, -60.0),
280+
(-120.0, -5.0),
281+
(-180.0, -5.0),
282+
(-180.0, -60.0),
283+
]
284+
),
285+
]
286+
)
316287

317288

318289
class TCTracks:
@@ -444,14 +415,14 @@ def subset(self, filterdict):
444415

445416
return out
446417

447-
def split_by_basin(self):
418+
def subset_by_basin(self):
448419
"""Subset all tropical cyclones tracks by basin.
449420
450421
This function iterates through the tropical cyclones in the dataset and assigns each cyclone
451-
to a basin based on its geographical location. It checks whether the cyclone's position (latitude
452-
and longitude) lies within the boundaries of any of the predefined basins and then groups the cyclones
453-
into separate categories for each basin. The resulting dictionary maps each basin's name to a list of
454-
tropical cyclones that fall within it.
422+
to a basin based on its geographical location. It checks whether the cyclone's position
423+
(latitude and longitude) lies within the boundaries of any of the predefined basins and
424+
then groups the cyclones into separate categories for each basin. The resulting dictionary
425+
maps each basin's name to a list of tropical cyclones that fall within it.
455426
456427
Parameters
457428
----------
@@ -474,19 +445,31 @@ def split_by_basin(self):
474445

475446
# Initialize a defaultdict to store lists for each basin
476447
basins_dict = defaultdict(list)
477-
448+
tracks_outside_basin: list = []
478449
# Iterate over each tropical cyclone
479450
for tc in self.data:
480451
lat, lon = tc.lat.values[0], tc.lon.values[0]
481452
origin_point = Point(lon, lat)
453+
point_in_basin = False
482454

483455
# Find the basin that contains the point
484-
for basin in BASINS.values():
485-
if basin.contains(origin_point):
456+
for basin in Basin:
457+
if basin.value.contains(origin_point):
486458
basins_dict[basin.name].append(tc)
459+
point_in_basin = True
487460
break
488461

489-
# Now create a dictionary with TCTracks for each basin
462+
if not point_in_basin:
463+
tracks_outside_basin.append(tc.id_no)
464+
465+
if tracks_outside_basin:
466+
warnings.warn(
467+
f"A total of {len(tracks_outside_basin)} tracks did not originate in any of the "
468+
f"defined basins. IDs of the tracks outside the basins: {tracks_outside_basin}",
469+
UserWarning,
470+
)
471+
472+
# Create a dictionary with TCTracks for each basin
490473
dict_tc_basins = {
491474
basin_name: TCTracks(tc_list) for basin_name, tc_list in basins_dict.items()
492475
}

0 commit comments

Comments
 (0)