diff --git a/stormevents/stormevent.py b/stormevents/stormevent.py index 2e90b54..d732aae 100644 --- a/stormevents/stormevent.py +++ b/stormevents/stormevent.py @@ -7,6 +7,7 @@ import pandas import xarray +from pandas import Series from shapely import ops from shapely.geometry import MultiPolygon from shapely.geometry import Polygon @@ -49,12 +50,15 @@ def __init__( year: int, start_date: datetime = None, end_date: datetime = None, + synthetic: bool = False, + **kwargs, ): """ :param name: storm name :param year: storm year :param start_date: starting time :param end_date: ending time + :param synthetic: whether the storm actually exists; `True` will skip lookup in the storm table >>> StormEvent('florence', 2018) 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__( StormEvent(name='IDA', year=2021, start_date=Timestamp('2021-08-27 18:00:00'), end_date=Timestamp('2021-08-29 18:00:00')) """ - storms = nhc_storms(year=year) - storms = storms[storms["name"].str.contains(name.upper())] - if len(storms) > 0: - self.__entry = storms.iloc[0] + if not synthetic: + storms = nhc_storms(year=year) + storms = storms[storms["name"].str.contains(name.upper())] + if len(storms) > 0: + self.__entry = storms.iloc[0] + else: + raise ValueError(f'storm "{name} {year}" not found in NHC database') else: - raise ValueError(f'storm "{name} {year}" not found in NHC database') + self.__entry = Series( + { + "name": name, + "year": year, + "start_date": start_date, + "end_date": end_date, + **kwargs, + }, + index=None, + ) self.__usgs_id = None self.__is_usgs_flood_event = True self.__high_water_marks = None self.__previous_configuration = {"name": self.name, "year": self.year} + self.__synthetic = synthetic self.start_date = start_date self.end_date = end_date @@ -268,6 +285,10 @@ def status(self) -> StormStatus: else: return StormStatus.HISTORICAL + @property + def synthetic(self) -> bool: + return self.__synthetic + def track( self, start_date: datetime = None, @@ -514,3 +535,49 @@ def __repr__(self) -> str: f"end_date={repr(self.end_date)}" f")" ) + + def __copy__(self) -> "StormEvent": + return self.__class__( + self.name, + year=self.year, + start_date=self.start_date, + end_date=self.end_date, + ) + + def perturb( + self, + name: str = None, + year: int = None, + start_date: datetime = None, + end_date: datetime = None, + **kwargs, + ) -> "StormEvent": + """ + :param name: storm name + :param year: storm year + :param start_date: starting time + :param end_date: ending time + :return: a new synthetic storm based on parameters from the current storm + """ + + if name is None: + name = self.name + if year is None: + year = self.year + if start_date is None: + start_date = self.start_date + elif isinstance(start_date, timedelta): + start_date = self.start_date + start_date + if end_date is None: + end_date = self.end_date + elif isinstance(end_date, timedelta): + end_date = self.end_date + end_date + + return self.__class__( + name=name, + year=year, + start_date=start_date, + end_date=end_date, + synthetic=True, + **kwargs, + ) diff --git a/tests/test_stormevent.py b/tests/test_stormevent.py index 0f83cbd..17a0cc6 100644 --- a/tests/test_stormevent.py +++ b/tests/test_stormevent.py @@ -82,6 +82,38 @@ def test_storm_event_lookup(): assert ida2021.end_date == datetime(2021, 9, 4, 18) +def test_synthetic_stormevent(florence2018): + synth_1 = StormEvent("synth_1", year=2018, synthetic=True) + synth_2 = StormEvent( + "synth_2", + start_date=datetime(2019, 10, 2), + end_date=datetime(2019, 10, 10), + year=2019, + synthetic=True, + ) + synth_florence = florence2018.perturb(name="synth_florence") + + assert synth_1.name == "synth_1" + assert synth_1.year == 2018 + assert synth_1.nhc_code is None + assert synth_1.start_date is None + assert synth_1.end_date is None + assert synth_1.synthetic + + assert synth_2.name == "synth_2" + assert synth_2.year == 2019 + assert synth_2.nhc_code is None + assert synth_2.start_date == datetime(2019, 10, 2) + assert synth_2.end_date == datetime(2019, 10, 10) + assert synth_2.synthetic + + assert synth_florence.name == "synth_florence" + assert synth_florence.year == 2018 + assert synth_florence.start_date == florence2018.start_date + assert synth_florence.end_date == florence2018.end_date + assert synth_florence.synthetic + + def test_storm_event_time_interval(): florence2018 = StormEvent("florence", 2018, start_date=timedelta(days=-2)) paine2016 = StormEvent.from_nhc_code("EP172016", end_date=timedelta(days=1))