Skip to content

Commit

Permalink
feat: add plot subcommand (#13)
Browse files Browse the repository at this point in the history
* add "ww3 plot" subcommand

* fix bug related to error reporting of a bad date

* add an example that uses "ww3 plot"

* add matplotlib as a dependency

* add impage of hurricane julia

* add news fragments

[skip ci]
  • Loading branch information
mcflugen authored Jun 17, 2022
1 parent cf72101 commit c5dff68
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 3 deletions.
19 changes: 19 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ This will download new datasets as necessary and load the new data into the ``da
Example
-------

Plot data from the command line
```````````````````````````````

Running the following from the command line will plot the variable *significant wave height*
from the WAVEWATCH III *at_4m* grid. Note that the time of day (in this case, 15:00) is
separated from the date with a ``T`` (i.e. times can be given as ``YYYY-MM-DDTHH``)

.. code:: bash
$ ww3 plot --grid=at_4m --data-var=swh "2010-09-15T15"
.. image:: https://raw.githubusercontent.com/csdms/bmi-wavewatch3/main/docs/source/_static/hurrican_julia.png
:width: 100%
:alt: Hurricane Julia

Plot data from Python
`````````````````````

This example is similar to the previous but uses the *bmi_wavewatch3* Python interface.

.. code:: python
Expand Down
77 changes: 75 additions & 2 deletions bmi_wavewatch3/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
from multiprocessing import Pool, RLock

import click
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
from .downloader import WaveWatch3Downloader
from .errors import ChoiceError, DateValueError
from .source import SOURCES
from .wavewatch3 import WaveWatch3


out = partial(click.secho, bold=True, file=sys.stderr)
Expand All @@ -26,7 +28,7 @@
def validate_date(ctx, param, value):
source = SOURCES[ctx.parent.params["source"]]

for date_str in value:
for date_str in [value] if isinstance(value, str) else value:
try:
source.validate_date(date_str)
except DateValueError as error:
Expand All @@ -39,14 +41,38 @@ def validate_quantity(ctx, param, value):
if not value:
return sorted(source.QUANTITIES)

for quantity in value:
for quantity in [value] if isinstance(value, str) else value:
try:
source.validate_quantity(quantity)
except ChoiceError as error:
raise click.BadParameter(error)
return value


def validate_data_var(ctx, param, value):
data_var_to_quantity = {
"dirpw": "dp",
"swh": "hs",
"perpw": "tp",
"u": "wind",
"v": "wind",
"swdir": "pdir",
"swell": "phs",
"swper": "ptp",
}
source = SOURCES[ctx.parent.params["source"]]
try:
quantity = data_var_to_quantity[value]
except KeyError:
raise click.BadParameter(ChoiceError(value, list(data_var_to_quantity)))

try:
source.validate_quantity(quantity)
except ChoiceError as error:
raise click.BadParameter(error)
return value


def validate_grid(ctx, param, value):
source = SOURCES[ctx.parent.params["source"]]
if not value:
Expand Down Expand Up @@ -244,6 +270,53 @@ def clean(ctx, dry_run, cache_dir, yes):
out(f"Removed {len(cache_files)} files ({total_bytes} bytes)")


@ww3.command()
@click.argument("date", callback=validate_date)
@click.option("--grid", default=None, help="Grid to download", callback=validate_grid)
@click.option(
"--data-var",
help="Data variable to plot",
default="swh",
callback=validate_data_var,
)
@click.pass_context
def plot(ctx, date, grid, data_var):
"""Plot WAVEWATCH III data by date."""
verbose = ctx.parent.params["verbose"]
silent = ctx.parent.params["silent"]
source = ctx.parent.params["source"]

data_var_to_quantity = {
"dirpw": "dp",
"swh": "hs",
"perpw": "tp",
"u": "wind",
"v": "wind",
"swdir": "pdir",
"swell": "phs",
"swper": "ptp",
}
quantity = data_var_to_quantity[data_var]

if not silent:
out(f"source: {source}")
out(f"grid: {grid}")
out(f"date: {date}")
out(f"data_var: {data_var} ({quantity})")

ww3 = WaveWatch3(date, source=source, grid=grid)
if not silent and verbose:
[out(f"source file: {url}") for url in ww3._urls]

ww3.data
if not silent and verbose:
[out(f"cache file: {ww3._cache / url.filename}") for url in ww3._urls]

ww3.data[data_var][ww3.step, :, :].plot()
plt.gca().set_aspect(1)
plt.show()


def _retreive_urls(urls, disable=False, force=False):
tqdm.set_lock(RLock())
p = Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),))
Expand Down
2 changes: 1 addition & 1 deletion bmi_wavewatch3/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def validate_date(cls, date):
try:
datetime.datetime.fromisoformat(date)
except ValueError as error:
raise DateValueError(error)
raise DateValueError(str(error))
date_in_range_or_raise(date, lower=cls.MIN_DATE, upper=cls.MAX_DATE)

return date
Expand Down
Binary file added docs/source/_static/hurricane_julia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions news/13.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed a bug in the reporting of an error caused by an invalide datatime
string.

5 changes: 5 additions & 0 deletions news/13.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added a new subcommand, *plot*, to the *ww3* command-line program.
``ww3 plot`` with download (if the data files are not already cached) and
create a plot of the requested data.


1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies = [
"bmipy",
"cfgrib",
"click",
"matplotlib",
"tomli",
"tqdm",
"xarray",
Expand Down

0 comments on commit c5dff68

Please sign in to comment.