<jupyter_start><jupyter_text>Develop `tide_stn_water_level` Figure ModuleDevelopment of functions for `nowcast.figures.fvcom.tide_stn_water_level` web site figure module.<jupyter_code>from contextlib import suppress
from datetime import timedelta
from pathlib import Path
import shlex
import subprocess
from types import SimpleNamespace

import arrow
import matplotlib.dates
import matplotlib.pyplot as plt
import requests
import xarray
from salishsea_tools import data_tools
from salishsea_tools.places import PLACES

from nowcast.figures import shared
import nowcast.figures.website_theme
%matplotlib inline<jupyter_output><empty_output><jupyter_text>`_get_ssh_forecast()` Function<jupyter_code>def _get_nemo_ssh(place, dataset_url_tmpl):
    ## TODO: This is a work-around because neither netCDF4 nor xarray are able
    ## to load the dataset directly from the URL due to an OpenDAP issue
    dataset_url = dataset_url_tmpl.format(place=place.replace(" ", ""))
    dataset_id = dataset_url.rsplit('/', 1)[1]
    ssh_file = Path('/tmp').joinpath(dataset_id).with_suffix('.nc')
    with ssh_file.open('wb') as f:
        resp = requests.get(f'{dataset_url}.nc')
        f.write(resp.content)
    try:
        nemo_ssh = xarray.open_dataset(ssh_file)
    except OSError:
        raise ValueError(f'NEMO ssh dataset not found for {place}')
    return nemo_ssh<jupyter_output><empty_output><jupyter_text>`_prep_plot_data()` Function<jupyter_code>def _prep_plot_data(place, fvcom_ssh_dataset, nemo_ssh_dataset_url_tmpl):
    # FVCOM sea surface height dataset
    fvcom_ssh = fvcom_ssh_dataset.zeta.isel(
        station=[
            name.decode().strip().split(maxsplit=1)[1] 
            for name in fvcom_ssh_dataset.name_station.values
        ].index(place))
    fvcom_period = slice(
        str(fvcom_ssh.time.values[0]), str(fvcom_ssh.time.values[-1])
    )
    # NEMO sea surface height dataset
    try:
        nemo_ssh = _get_nemo_ssh(place, nemo_ssh_dataset_url_tmpl).sel(time=fvcom_period)
    except ValueError:
        # No NEMO sea surface height dataset for place
        nemo_ssh = None
    # CHS water level observations dataset
    try:
        obs_1min = data_tools.get_chs_tides(
            'obs',
            place,
            arrow.get(fvcom_period.start) - timedelta(seconds=5 * 60),
            arrow.get(fvcom_period.stop)
        )
        obs = xarray.Dataset({
            'water_level': xarray.DataArray(obs_1min).rename({
                'dim_0': 'time'
            })
        })
    except TypeError:
        # Invalid tide gauge station number, probably None
        obs = None
    # CHS water level predictions dataset
    try:
        pred_place = 'Point Atkinson' if place == 'Sandy Cove' else place
        pred = data_tools.get_chs_tides(
            'pred', pred_place, arrow.get(fvcom_period.start),
            arrow.get(fvcom_period.stop)
        )
        pred = xarray.Dataset({
            'water_level':
                xarray.DataArray.from_series(pred).rename({
                    'index': 'time'
                })
        })
    except TypeError:
        # Invalid tide gauge station number, probably None
        pred = None
    # Change dataset times to Pacific time zone
    shared.localize_time(fvcom_ssh)
    with suppress(AttributeError):
        shared.localize_time(nemo_ssh)
    with suppress(IndexError, AttributeError):
        shared.localize_time(obs)
    with suppress(IndexError, AttributeError):
        shared.localize_time(pred)
    # Mean sea level
    msl = PLACES[place]['mean sea lvl']
    return SimpleNamespace(
        fvcom_ssh=fvcom_ssh,
        nemo_ssh=nemo_ssh,
        obs=obs,
        pred=pred,
        msl=msl,
    )<jupyter_output><empty_output><jupyter_text>`_prep_fig_axes()` Function<jupyter_code>def _prep_fig_axes(figsize, theme):
    fig, ax = plt.subplots(
        1, 1, figsize=figsize, facecolor=theme.COLOURS['figure']['facecolor']
    )
    fig.autofmt_xdate()
    return fig, ax<jupyter_output><empty_output><jupyter_text>`_plot_water_level_time_series()` Function<jupyter_code>def _plot_water_level_time_series(ax, place, plot_data, theme):
    with suppress(AttributeError):
        # CHS sometimes returns an empty prediction dataset
        if plot_data.pred.water_level.size:
            plot_data.pred.water_level.plot(
                ax=ax,
                linewidth=2,
                label='CHS Predicted',
                color=theme.COLOURS['time series']['tidal prediction']
            )
    with suppress(AttributeError):
        # CHS sometimes returns an empty observations dataset
        if plot_data.obs.water_level.size:
            plot_data.obs.water_level.plot(
                ax=ax,
                linewidth=2,
                label='CHS Observed',
                color=theme.COLOURS['time series']['tide gauge obs']
            )
    with suppress(AttributeError):
        (plot_data.nemo_ssh.ssh + plot_data.msl).plot(
            ax=ax,
            linewidth=2,
            label='NEMO',
            color=theme.COLOURS['time series']['tide gauge ssh']
        )
    (plot_data.fvcom_ssh + plot_data.msl).plot(
        ax=ax,
        linewidth=2,
        label='FVCOM',
        color=theme.COLOURS['time series']['vhfr fvcom ssh']
    )
    legend = ax.legend(prop=theme.FONTS['legend label small'])
    legend.set_title('Legend', prop=theme.FONTS['legend title small'])
    _water_level_time_series_labels(ax, place, plot_data, theme)<jupyter_output><empty_output><jupyter_text>`_water_level_time_series_labels()` Function<jupyter_code>def _water_level_time_series_labels(ax, place, plot_data, theme):
    ax.set_title(
        f'Water Level at {place}',
        fontproperties=theme.FONTS['axes title'],
        color=theme.COLOURS['text']['axes title']
    )
    ax.set_xlabel(
        f'Time [{plot_data.fvcom_ssh.attrs["tz_name"]}]',
        fontproperties=theme.FONTS['axis'],
        color=theme.COLOURS['text']['axis']
    )
    ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%d%b %H:%M'))
    ax.set_ylabel(
        'Water Level above Chart Datum [m]',
        fontproperties=theme.FONTS['axis'],
        color=theme.COLOURS['text']['axis']
    )
    ax.grid(axis='both')
    theme.set_axis_colors(ax)<jupyter_output><empty_output><jupyter_text>`make_figure()` FunctionThis is is the function that will be called by the `nowcast.workers.make_plots` worker to return a `matplotlib.figure.Figure` object.<jupyter_code>def make_figure(place,
                fvcom_ssh_dataset_path,
                nemo_ssh_dataset_url_tmpl,
                figsize=(16, 9),
                theme=nowcast.figures.website_theme):
    plot_data = _prep_plot_data(place, fvcom_ssh_dataset_path, nemo_ssh_dataset_url_tmpl)
    fig, ax = _prep_fig_axes(figsize, theme)
    _plot_water_level_time_series(ax, place, plot_data, theme)
    return fig<jupyter_output><empty_output><jupyter_text>Render the FigureThe `%%timeit` cell magic lets us keep an eye on how log the figure takes to process.Setting `-n1 -r1` prevents it from processing the figure more than onceas it might try to do to generate better statistics.  Nowcast Figure<jupyter_code>%%timeit -n1 -r1

from importlib import reload
from nowcast.figures import website_theme
from salishsea_tools import places
reload(website_theme)
reload(places)

run_type = 'nowcast'
run_date = arrow.get('2018-05-09')

fvcom_ssh_dataset_path_tmpl = '/opp/fvcom/{run_type}/{ddmmmyy}/vhfr_low_v2_station_timeseries.nc'
if run_type == 'nowcast':
    fvcom_ssh_dataset_path = Path(
        fvcom_ssh_dataset_path_tmpl.format(run_type=run_type, ddmmmyy=run_date.format("DDMMMYY").lower())
    )
else:
    nowcast_dataset_path = Path(
        fvcom_ssh_dataset_path_tmpl.format(run_type=run_type, ddmmmyy=run_date.format("DDMMMYY").lower())
    )
    forecast_dataset_path = Path(
        fvcom_ssh_dataset_path_tmpl.format(run_type=run_type, ddmmmyy=run_date.replace(days=+1).format("DDMMMYY").lower())
    )
    fvcom_ssh_dataset_path = '/tmp/vhfr_low_v2_station_timeseries_forecast.nc'
    cmd = f'ncrcat {nowcast_dataset_path} {forecast_dataset_path} -o {fvcom_ssh_dataset_path}'
    subprocess.check_output(shlex.split(cmd))
cmd = (
    f'ncrename -O -v siglay,sigma_layer -v siglev,sigma_level '
    f'{fvcom_ssh_dataset_path} /tmp/{fvcom_ssh_dataset_path.name}')
subprocess.check_output(shlex.split(cmd))
fvcom_ssh_dataset = xarray.open_dataset(f'/tmp/{fvcom_ssh_dataset_path.name}')

nemo_ssh_dataset_url_tmpl = 'https://salishsea.eos.ubc.ca/erddap/griddap/ubcSSf{place}SSH10mV17-02'

fig = make_figure('<NAME>', fvcom_ssh_dataset, nemo_ssh_dataset_url_tmpl)<jupyter_output>16 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)<jupyter_text>Forecast Figure<jupyter_code>%%timeit -n1 -r1

from importlib import reload
from nowcast.figures import website_theme
from salishsea_tools import places
reload(website_theme)
reload(places)

run_type = 'forecast'
run_date = arrow.get('2018-05-09')

fvcom_ssh_dataset_path_tmpl = '/opp/fvcom/{run_type}/{ddmmmyy}/vhfr_low_v2_station_timeseries.nc'
if run_type == 'nowcast':
    fvcom_ssh_dataset_path = Path(
        fvcom_ssh_dataset_path_tmpl.format(run_type=run_type, ddmmmyy=run_date.format("DDMMMYY").lower())
    )
else:
    nowcast_dataset_path = Path(
        fvcom_ssh_dataset_path_tmpl.format(run_type='nowcast', ddmmmyy=run_date.format("DDMMMYY").lower())
    )
    forecast_dataset_path = Path(
        fvcom_ssh_dataset_path_tmpl.format(run_type='forecast', ddmmmyy=run_date.format("DDMMMYY").lower())
    )
    fvcom_ssh_dataset_path = Path('/tmp/vhfr_low_v2_station_timeseries_forecast.nc')
    cmd = f'ncrcat -O {nowcast_dataset_path} {forecast_dataset_path} -o {fvcom_ssh_dataset_path}'
    subprocess.check_output(shlex.split(cmd))
cmd = (
    f'ncrename -O -v siglay,sigma_layer -v siglev,sigma_level '
    f'{fvcom_ssh_dataset_path} /tmp/{fvcom_ssh_dataset_path.name}')
subprocess.check_output(shlex.split(cmd))
fvcom_ssh_dataset = xarray.open_dataset(f'/tmp/{fvcom_ssh_dataset_path.name}')

nemo_ssh_dataset_url_tmpl = 'https://salishsea.eos.ubc.ca/erddap/griddap/ubcSSf{place}SSH10mV17-02'

fig = make_figure('<NAME>', fvcom_ssh_dataset, nemo_ssh_dataset_url_tmpl)<jupyter_output>16.8 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
{'fraction_non_alphanumeric': 0.1074934395956847, 'fraction_numerical': 0.00699776460297405, 'mean_word_length': 3.6561085972850678, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '25400', 'license': 'Apache-2.0', 'n_tokens_mistral': 3722, 'n_tokens_neox': 3500, 'n_words': 626, 'path': 'notebooks/figures/fvcom/publish/DevelopTideStnWaterLevel.ipynb', 'repo_name': 'SalishSeaCast/SalishSeaNowcast', 'size': '10297'}