Skip to content

Commit

Permalink
Add support for forecast time (#82)
Browse files Browse the repository at this point in the history
* Add support for forecast time
* Pin sphinx to pre 7 due to rtd theme
* Add USGS event code for typhoon
  • Loading branch information
SorooshMani-NOAA authored Jun 22, 2023
1 parent 197da88 commit 6aceb98
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dynamic = ["version"]
[project.optional-dependencies]
test = ['pytest', 'pytest-cov', 'pytest-socket', 'pytest-xdist']
style = ['black', 'reorder-python-imports']
docs = ['dunamai', 'm2r2', 'sphinx', 'sphinx-rtd-theme', 'toml']
docs = ['dunamai', 'm2r2', 'sphinx<7', 'sphinx-rtd-theme', 'toml']

[project.urls]
repository = 'https://github.com/oceanmodeling/StormEvents.git'
Expand Down
39 changes: 39 additions & 0 deletions stormevents/nhc/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __init__(
end_date: datetime = None,
file_deck: ATCF_FileDeck = None,
advisories: List[ATCF_Advisory] = None,
forecast_time: datetime = None,
):
"""
:param storm: storm ID, or storm name and year
Expand Down Expand Up @@ -80,6 +81,7 @@ def __init__(
self.__end_date = None
self.__file_deck = None
self.__advisories = None
self.__forecast_time = None

self.__advisories_to_remove = []
self.__invalid_storm_name = False
Expand Down Expand Up @@ -107,6 +109,7 @@ def __init__(
# use start and end dates to mask dataframe here
self.start_date = start_date
self.end_date = end_date
self.forecast_time = forecast_time

@classmethod
def from_storm_name(
Expand All @@ -117,6 +120,7 @@ def from_storm_name(
end_date: datetime = None,
file_deck: ATCF_FileDeck = None,
advisories: List[ATCF_Advisory] = None,
forecast_time: datetime = None,
) -> "VortexTrack":
"""
:param name: storm name
Expand All @@ -139,6 +143,7 @@ def from_storm_name(
end_date=end_date,
file_deck=file_deck,
advisories=advisories,
forecast_time=forecast_time,
)

@classmethod
Expand All @@ -149,6 +154,7 @@ def from_file(
end_date: datetime = None,
file_deck: ATCF_FileDeck = None,
advisories: List[ATCF_Advisory] = None,
forecast_time: datetime = None,
) -> "VortexTrack":
"""
:param path: file path to ATCF data
Expand Down Expand Up @@ -179,6 +185,7 @@ def from_file(
end_date=end_date,
file_deck=file_deck,
advisories=advisories,
forecast_time=forecast_time,
)

@property
Expand Down Expand Up @@ -372,6 +379,32 @@ def end_date(self, end_date: datetime):

self.__end_date = end_date

@property
def forecast_time(self) -> pandas.Timestamp:
"""
:return: forecast time of forecast track
"""

return self.__forecast_time

@forecast_time.setter
def forecast_time(self, forecast_time: datetime):
if forecast_time is not None:
forecast_time = pandas.to_datetime(forecast_time)

if self.file_deck != ATCF_FileDeck.ADVISORY:
raise ValueError("Forecast time only applies to forecast advisories")

# NOTE: Cannot cleanly check forecast_time against
# start and end date, since the first is on `track_start_time`
# and the latter two on `datetime`
# if not (self.start_date < forecast_time < self.end_date):
# raise ValueError(
# "The specified forecast time is outside available date range"
# )

self.__forecast_time = forecast_time

@property
def file_deck(self) -> ATCF_FileDeck:
"""
Expand Down Expand Up @@ -490,6 +523,12 @@ def data(self) -> DataFrame:
[10434 rows x 38 columns]
"""

if self.forecast_time is not None:
return self.unfiltered_data.loc[
(self.unfiltered_data["track_start_time"] == self.forecast_time)
& (self.unfiltered_data["datetime"] >= self.start_date)
& (self.unfiltered_data["datetime"] <= self.end_date)
]
return self.unfiltered_data.loc[
(self.unfiltered_data["datetime"] >= self.start_date)
& (self.unfiltered_data["datetime"] <= self.end_date)
Expand Down
2 changes: 2 additions & 0 deletions stormevents/stormevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def track(
file_deck: ATCF_FileDeck = None,
advisories: List[ATCF_Advisory] = None,
filename: PathLike = None,
forecast_time: datetime = None,
) -> VortexTrack:
"""
retrieve NHC ATCF track data
Expand Down Expand Up @@ -309,6 +310,7 @@ def track(
end_date=end_date,
file_deck=file_deck,
advisories=advisories,
forecast_time=forecast_time,
)
return track

Expand Down
1 change: 1 addition & 0 deletions stormevents/usgs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class EventType(Enum):
DROUGHT = 3
NOREASTER = 4
TSUNAMI = 6
TYPHOON = 7


class EventStatus(Enum):
Expand Down
89 changes: 89 additions & 0 deletions tests/test_nhc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import timedelta

import numpy
import pandas
import pytest
from pytest_socket import SocketBlockedError

Expand Down Expand Up @@ -272,6 +273,94 @@ def test_vortex_track_no_internet():
check_reference_directory(output_directory, reference_directory)


def test_vortex_track_forecast_time_init_arg():
# Test __init__ to accept forecast_time argument
track = VortexTrack(
storm="al062018", advisories=["OFCL"], file_deck="a", forecast_time="09-10-2018"
)

dates = track.data.track_start_time.unique()
assert len(dates) == 1
assert pandas.to_datetime(dates) == pandas.to_datetime("09-10-2018")


def test_vortex_track_forecast_time_fromname_arg():
# Test from_storm_name to accept forecast_time argument
track = VortexTrack.from_storm_name(
"Florence", 2018, advisories=["OFCL"], file_deck="a", forecast_time="09-10-2018"
)

dates = track.data.track_start_time.unique()
assert len(dates) == 1
assert pandas.to_datetime(dates) == pandas.to_datetime("09-10-2018")


def test_vortex_track_forecast_time_fromfile_arg():
# Test from_file to accept forecast_time argument
input_directory = INPUT_DIRECTORY / "test_vortex_track_from_file"

track = VortexTrack.from_file(
input_directory / "AL062018.dat", file_deck="a", forecast_time="09-10-2018"
)

dates = track.data.track_start_time.unique()
assert len(dates) == 1
assert pandas.to_datetime(dates) == pandas.to_datetime("09-10-2018")


# def test_vortex_track_forecast_time_outofbound_date():
# # Test it raises if forecast time is not between start and end
# msg = ""
# try:
# VortexTrack(
# "al062018", advisories=["OFCL"], file_deck="a", forecast_time="07-15-2018"
# )
# except ValueError as e:
# msg = str(e)
#
# assert "forecast time is outside available" in msg


def test_vortex_track_forecast_time_nonforecast_track():
# Test it raises if a non-forecast track is requested but forecast
# time is specified
msg = ""
try:
VortexTrack(
"al062018", advisories=["OFCL"], file_deck="b", forecast_time="07-15-2018"
)
except ValueError as e:
msg = str(e)

assert "only applies to forecast" in msg


def test_vortex_track_forecast_time_set_value():
track = VortexTrack.from_storm_name(
"Florence",
2018,
advisories=["OFCL"],
file_deck="a",
)
track.forecast_time = "09-10-2018"

dates = track.data.track_start_time.unique()

assert len(dates) == 1
assert pandas.to_datetime(dates) == pandas.to_datetime("09-10-2018")


def test_vortex_track_forecast_time_unset_value():
track = VortexTrack.from_storm_name(
"Florence", 2018, advisories=["OFCL"], file_deck="a", forecast_time="09-10-2018"
)
track.forecast_time = None

dates = track.data.track_start_time.unique()

assert len(dates) > 1


def test_carq_autofix_ofcl():
track = VortexTrack.from_storm_name(
"Florence", 2018, advisories=["OFCL"], file_deck="a"
Expand Down
5 changes: 5 additions & 0 deletions tests/test_stormevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ def test_storm_event_track(florence2018, ida2021):
check_reference_directory(output_directory, reference_directory)


def test_storm_event_track_forecast_time(florence2018):
florence_track = florence2018.track(file_deck="a", forecast_time="09-10-2018")
assert len(florence_track.data.track_start_time.unique()) == 1


def test_storm_event_high_water_marks(florence2018):
reference_directory = REFERENCE_DIRECTORY / "test_storm_event_high_water_marks"
output_directory = OUTPUT_DIRECTORY / "test_storm_event_high_water_marks"
Expand Down

0 comments on commit 6aceb98

Please sign in to comment.