import datetime
import pathlib
import urllib
from .errors import ChoiceError, DateValueError
class _WaveWatch3Source:
SCHEME = "https"
NETLOC = ""
PREFIX = ""
GRIDS = {
"glo_30m",
"ao_30m",
"at_10m",
"wc_10m",
"ep_10m",
"ak_10m",
"at_4m",
"wc_4m",
"ak_4m",
}
QUANTITIES = {"wind", "hs", "tp", "dp"}
MIN_DATE = None
MAX_DATE = None
def __init__(self, date, quantity, grid="glo_30m"):
self._date = datetime.datetime.fromisoformat(self.validate_date(date))
self._quantity = self.validate_quantity(quantity)
self._grid = self.validate_grid(grid)
@property
def path(self):
raise NotImplementedError("path")
@property
def filename(self):
raise NotImplementedError("filename")
def __str__(self):
return urllib.parse.urlunparse(
[
self.SCHEME,
self.NETLOC,
str(self.path),
"",
"",
"",
]
)
def __repr__(self):
date = self._date.isoformat()
return f"{self.__class__.__module__}.{self.__class__.__name__}({date!r}, {self.quantity!r}, grid={self.grid!r})"
def __eq__(self, other):
return str(self) == str(other)
@property
def grid(self):
return self._grid
@grid.setter
def grid(self, grid):
self.validate_grid(grid)
self._grid = grid
@property
def date(self):
return self._date.isoformat(timespec="hours")
@date.setter
def date(self, date):
self._date = datetime.datetime.fromisoformat(self.validate_date(date))
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, quantity):
self._quantity = self.validate_quantity(quantity)
@property
def year(self):
return self._date.year
@year.setter
def year(self, year):
try:
self._date = datetime.date(year, self.month, self._date.day)
except ValueError as error:
raise DateValueError(str(error))
@property
def month(self):
return self._date.month
@month.setter
def month(self, month):
try:
self._date = datetime.datetime(self.year, month, self._date.day)
except ValueError as error:
raise DateValueError(str(error))
@classmethod
def validate_quantity(cls, quantity):
if quantity not in cls.QUANTITIES:
raise ChoiceError(quantity, cls.QUANTITIES)
return quantity
@classmethod
def validate_grid(cls, grid):
if grid not in cls.GRIDS:
raise ChoiceError(grid, cls.GRIDS)
return grid
@classmethod
def validate_date(cls, date):
try:
datetime.datetime.fromisoformat(date)
except ValueError as error:
raise DateValueError(str(error))
date_in_range_or_raise(date, lower=cls.MIN_DATE, upper=cls.MAX_DATE)
return date
[docs]def date_in_range_or_raise(date, lower=None, upper=None):
date = datetime.datetime.fromisoformat(date)
lower = datetime.datetime.fromisoformat(lower) if lower else lower
if lower and date < lower:
raise DateValueError(
f"{date}: date is less than minimum date for this dataset ({lower})"
)
upper = datetime.datetime.fromisoformat(upper) if upper else upper
if upper and date > upper:
raise DateValueError(
f"{date}: date is greater than maximum date for this dataset ({upper})"
)
[docs]class WaveWatch3SourcePhase1(_WaveWatch3Source):
"""
https://polar.ncep.noaa.gov/waves/hindcasts/nopp-phase1.php
"""
SCHEME = "https"
NETLOC = "polar.ncep.noaa.gov"
PREFIX = "/waves/hindcasts/nopp-phase1"
GRIDS = {
"ak_4m",
"ak_10m",
"ecg_4m",
"ecg_10m",
# "glo_30m_ext",
"glo_30m",
"med_10m",
"nsb_4m",
"nsb_10m",
"nwio_10m",
"oz_4m",
"oz_10m",
"pi_10m",
"wc_4m",
"wc_10m",
}
MIN_DATE = "1979-01-01"
MAX_DATE = "2009-12-31"
def __init__(self, date, quantity, grid="glo_30m"):
super().__init__(date, quantity, grid=grid)
@property
def path(self):
path = (
self.PREFIX / pathlib.PurePosixPath(f"{self.year}{self.month:02d}") / "grib"
)
return path / self.filename
@property
def filename(self):
return f"multi_reanal.{self.grid}.{self.quantity}.{self.year}{self.month:02d}.grb2.gz"
[docs]class WaveWatch3SourcePhase2(_WaveWatch3Source):
"""
https://polar.ncep.noaa.gov/waves/hindcasts/nopp-phase2.php
"""
SCHEME = "https"
NETLOC = "polar.ncep.noaa.gov"
PREFIX = "/waves/hindcasts/nopp-phase2"
GRIDS = {
"ak_4m",
"ak_10m",
"ecg_4m",
"ecg_10m",
"glo_30m_ext",
"med_10m",
"nsb_4m",
"nsb_10m",
"nwio_10m",
"oz_4m",
"oz_10m",
"pi_10m",
"wc_4m",
"wc_10m",
}
MIN_DATE = "1979-01-01"
MAX_DATE = "2009-12-31"
def __init__(self, date, quantity, grid="glo_30m_ext"):
super().__init__(date, quantity, grid=grid)
@property
def path(self):
path = (
self.PREFIX
/ pathlib.PurePosixPath(f"{self.year}{self.month:02d}")
/ "gribs"
)
return path / self.filename
@property
def filename(self):
return (
f"multi_reanal.{self.grid}.{self.quantity}.{self.year}{self.month:02d}.grb2"
)
[docs]class WaveWatch3SourceMultigrid(_WaveWatch3Source):
"""
https://polar.ncep.noaa.gov/waves/hindcasts/prod-multi_1.php
"""
SCHEME = "https"
NETLOC = "polar.ncep.noaa.gov"
PREFIX = "/waves/hindcasts/multi_1"
MIN_DATE = "2005-02-01"
MAX_DATE = "2019-05-31"
@property
def path(self):
path = (
self.PREFIX
/ pathlib.PurePosixPath(f"{self.year}{self.month:02d}")
/ "gribs"
)
return path / self.filename
@property
def filename(self):
return f"multi_1.{self.grid}.{self.quantity}.{self.year}{self.month:02d}.grb2"
[docs]class WaveWatch3SourceMultigridExt(_WaveWatch3Source):
"""
https://polar.ncep.noaa.gov/waves/hindcasts/prod-multi_1.php
"""
SCHEME = "https"
NETLOC = "polar.ncep.noaa.gov"
PREFIX = "/waves/hindcasts/multi_1"
GRIDS = {
"glo_30m",
"ao_30m",
"at_10m",
"wc_10m",
"ep_10m",
"ak_10m",
"at_4m",
"wc_4m",
"ak_4m",
}
QUANTITIES = {"wind", "hs", "tp", "dp", "phs", "ptp", "pdir"} # added 2017 onward
MIN_DATE = "2017-02-01"
MAX_DATE = "2019-05-31"
@property
def path(self):
path = (
self.PREFIX
/ pathlib.PurePosixPath(f"{self.year}{self.month:02d}")
/ "gribs"
)
return path / self.filename
@property
def filename(self):
return f"multi_1.{self.grid}.{self.quantity}.{self.year}{self.month:02d}.grb2"
[docs]class WaveWatch3SourceThredds(_WaveWatch3Source):
SCHEME = "https"
NETLOC = "www.ncei.noaa.gov"
PREFIX = "/thredds-ocean/fileServer/ncep/nww3"
MIN_DATE = "2005-02-01"
MAX_DATE = "2019-05-31"
@property
def path(self):
path = self.PREFIX / pathlib.PurePosixPath(f"{self.year}", f"{self.month:02d}")
path /= self.grid if self.year < 2017 else "gribs"
return path / self.filename
@property
def filename(self):
return f"multi_1.{self.grid}.{self.quantity}.{self.year}{self.month:02d}.grb2"
[docs]class WaveWatch3SourceSinglegrid(_WaveWatch3Source):
"""
https://polar.ncep.noaa.gov/waves/hindcasts/prod-nww3.php
"""
SCHEME = "https"
NETLOC = "polar.ncep.noaa.gov"
PREFIX = "/waves/hindcasts/nww3"
GRIDS = {"akw", "enp", "nah", "nph", "nww3", "wna"}
MIN_DATE = "1999-07-01"
MAX_DATE = "2006-09-30"
def __init__(self, date, quantity, grid="nww3"):
super().__init__(date, quantity, grid=grid)
@property
def path(self):
return pathlib.PurePosixPath(self.PREFIX) / self.filename
@property
def filename(self):
return f"{self.grid}.{self.quantity}.{self.year}{self.month:02d}.grb"
SOURCES = {
"multigrid": WaveWatch3SourceMultigrid,
"multigrid-extended": WaveWatch3SourceMultigridExt,
"multigrid-thredds": WaveWatch3SourceThredds,
"phase1": WaveWatch3SourcePhase1,
"phase2": WaveWatch3SourcePhase2,
"singlegrid": WaveWatch3SourceSinglegrid,
}