Skip to content

Commit 0759960

Browse files
author
zacharyburnett
committed
add ability to create a synthetic stormevent
1 parent bda7df9 commit 0759960

File tree

2 files changed

+104
-5
lines changed

2 files changed

+104
-5
lines changed

stormevents/stormevent.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pandas
99
import xarray
10+
from pandas import Series
1011
from shapely import ops
1112
from shapely.geometry import MultiPolygon
1213
from shapely.geometry import Polygon
@@ -49,12 +50,15 @@ def __init__(
4950
year: int,
5051
start_date: datetime = None,
5152
end_date: datetime = None,
53+
synthetic: bool = False,
54+
**kwargs,
5255
):
5356
"""
5457
:param name: storm name
5558
:param year: storm year
5659
:param start_date: starting time
5760
:param end_date: ending time
61+
:param synthetic: whether the storm actually exists; `True` will skip lookup in the storm table
5862
5963
>>> StormEvent('florence', 2018)
6064
StormEvent(name='FLORENCE', year=2018, start_date=Timestamp('2018-08-30 06:00:00'), end_date=Timestamp('2018-09-18 12:00:00'))
@@ -72,17 +76,30 @@ def __init__(
7276
StormEvent(name='IDA', year=2021, start_date=Timestamp('2021-08-27 18:00:00'), end_date=Timestamp('2021-08-29 18:00:00'))
7377
"""
7478

75-
storms = nhc_storms(year=year)
76-
storms = storms[storms["name"].str.contains(name.upper())]
77-
if len(storms) > 0:
78-
self.__entry = storms.iloc[0]
79+
if not synthetic:
80+
storms = nhc_storms(year=year)
81+
storms = storms[storms["name"].str.contains(name.upper())]
82+
if len(storms) > 0:
83+
self.__entry = storms.iloc[0]
84+
else:
85+
raise ValueError(f'storm "{name} {year}" not found in NHC database')
7986
else:
80-
raise ValueError(f'storm "{name} {year}" not found in NHC database')
87+
self.__entry = Series(
88+
{
89+
"name": name,
90+
"year": year,
91+
"start_date": start_date,
92+
"end_date": end_date,
93+
**kwargs,
94+
},
95+
index=None,
96+
)
8197

8298
self.__usgs_id = None
8399
self.__is_usgs_flood_event = True
84100
self.__high_water_marks = None
85101
self.__previous_configuration = {"name": self.name, "year": self.year}
102+
self.__synthetic = synthetic
86103

87104
self.start_date = start_date
88105
self.end_date = end_date
@@ -268,6 +285,10 @@ def status(self) -> StormStatus:
268285
else:
269286
return StormStatus.HISTORICAL
270287

288+
@property
289+
def synthetic(self) -> bool:
290+
return self.__synthetic
291+
271292
def track(
272293
self,
273294
start_date: datetime = None,
@@ -514,3 +535,49 @@ def __repr__(self) -> str:
514535
f"end_date={repr(self.end_date)}"
515536
f")"
516537
)
538+
539+
def __copy__(self) -> "StormEvent":
540+
return self.__class__(
541+
self.name,
542+
year=self.year,
543+
start_date=self.start_date,
544+
end_date=self.end_date,
545+
)
546+
547+
def perturb(
548+
self,
549+
name: str = None,
550+
year: int = None,
551+
start_date: datetime = None,
552+
end_date: datetime = None,
553+
**kwargs,
554+
) -> "StormEvent":
555+
"""
556+
:param name: storm name
557+
:param year: storm year
558+
:param start_date: starting time
559+
:param end_date: ending time
560+
:return: a new synthetic storm based on parameters from the current storm
561+
"""
562+
563+
if name is None:
564+
name = self.name
565+
if year is None:
566+
year = self.year
567+
if start_date is None:
568+
start_date = self.start_date
569+
elif isinstance(start_date, timedelta):
570+
start_date = self.start_date + start_date
571+
if end_date is None:
572+
end_date = self.end_date
573+
elif isinstance(end_date, timedelta):
574+
end_date = self.end_date + end_date
575+
576+
return self.__class__(
577+
name=name,
578+
year=year,
579+
start_date=start_date,
580+
end_date=end_date,
581+
synthetic=True,
582+
**kwargs,
583+
)

tests/test_stormevent.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,38 @@ def test_storm_event_lookup():
8282
assert ida2021.end_date == datetime(2021, 9, 4, 18)
8383

8484

85+
def test_synthetic_stormevent(florence2018):
86+
synth_1 = StormEvent("synth_1", year=2018, synthetic=True)
87+
synth_2 = StormEvent(
88+
"synth_2",
89+
start_date=datetime(2019, 10, 2),
90+
end_date=datetime(2019, 10, 10),
91+
year=2019,
92+
synthetic=True,
93+
)
94+
synth_florence = florence2018.perturb(name="synth_florence")
95+
96+
assert synth_1.name == "synth_1"
97+
assert synth_1.year == 2018
98+
assert synth_1.nhc_code is None
99+
assert synth_1.start_date is None
100+
assert synth_1.end_date is None
101+
assert synth_1.synthetic
102+
103+
assert synth_2.name == "synth_2"
104+
assert synth_2.year == 2019
105+
assert synth_2.nhc_code is None
106+
assert synth_2.start_date == datetime(2019, 10, 2)
107+
assert synth_2.end_date == datetime(2019, 10, 10)
108+
assert synth_2.synthetic
109+
110+
assert synth_florence.name == "synth_florence"
111+
assert synth_florence.year == 2018
112+
assert synth_florence.start_date == florence2018.start_date
113+
assert synth_florence.end_date == florence2018.end_date
114+
assert synth_florence.synthetic
115+
116+
85117
def test_storm_event_time_interval():
86118
florence2018 = StormEvent("florence", 2018, start_date=timedelta(days=-2))
87119
paine2016 = StormEvent.from_nhc_code("EP172016", end_date=timedelta(days=1))

0 commit comments

Comments
 (0)