Real-time flow forecasts with ECCC weather forecasts
This notebook shows how to perform a streamflow forecast, using ECCC weather forecasts. Generates the forecasts and plots them.
%matplotlib inline
import datetime as dt
# Import the required packages
import warnings
import fiona
import matplotlib.pyplot as plt
import xarray as xr
# ignore warnings from xsdba
warnings.simplefilter("ignore", category=UserWarning)
from ravenpy import Emulator
from ravenpy.config import commands as rc
from ravenpy.config import emulators
from ravenpy.extractors.forecasts import get_recent_ECCC_forecast
# Utility that simplifies working with test data hosted on GitHub
from ravenpy.testing.utils import get_file
from ravenpy.utilities import forecasting
# Define the catchment contour. Here we use the Salmon River file we previously generated using the Delineator
# in Tutorial Notebook 01.
basin_contour = get_file("notebook_inputs/salmon_river.geojson")
# Get the most recent ECCC forecast data from the Geomet extraction tool:
forecast_data = get_recent_ECCC_forecast(
fiona.open(basin_contour), climate_model="GEPS"
)
display(forecast_data)
# We need to write the forecast data as a file for Raven to be able to access it.
fname = "/tmp/forecast.nc"
forecast_data.to_netcdf(fname)
# Define the warmup period dates. Our weather file ends before the forecast date so our states will not be as
# good as those of a model run operationally.
start_date_wu = dt.datetime(2010, 1, 1)
end_date_wu = dt.datetime(2020, 3, 30)
# Define some catchment properties. Could also be replaced by a call to the properties WPS as in
# the Tutorial Notebook 02.
hru = dict(
area=4250.6,
elevation=843.0,
latitude=54.4848,
longitude=-123.3659,
hru_type="land",
)
# Observed weather data for the Salmon river. We extracted this using Tutorial Notebook 03 and the
# salmon_river.geojson file as the contour. Used for the model warm-up.
ts = get_file("notebook_inputs/ERA5_weather_data_Salmon.nc")
# Set alternative names for netCDF variables
alt_names = {
"TEMP_MIN": "tmin",
"TEMP_MAX": "tmax",
"PRECIP": "pr",
}
# Data types to extract from netCDF
data_type = ["TEMP_MAX", "TEMP_MIN", "PRECIP"]
data_kwds = {
"ALL": {
"elevation": hru["elevation"],
"Latitude": hru["latitude"],
"Longitude": hru["longitude"],
},
}
# Model configuration
model_config_warmup = emulators.GR4JCN(
params=[0.529, -3.396, 407.29, 1.072, 16.9, 0.947],
Gauge=[
rc.Gauge.from_nc(
ts, data_type=data_type, alt_names=alt_names, data_kwds=data_kwds
)
],
HRUs=[hru],
StartDate=start_date_wu,
EndDate=end_date_wu,
RunName="ESP_vs_NWP_warmup",
)
# Run the model and get the outputs.
out1 = Emulator(config=model_config_warmup).run()
# Extract the path to the final states file that will be used as the next initial states
hotstart = out1.files["solution"]
# Length of the desired forecast, in days
duration = 7
# We need to adjust the data_type and alt_names according to the data in the forecast:
# Set alternative names for netCDF variables
alt_names = {
"TEMP_AVE": "tas",
"PRECIP": "pr",
}
# Data types to extract from netCDF
data_type = ["TEMP_AVE", "PRECIP"]
# We will need to reuse this for GR4J. Update according to your needs. For example, here we will also pass
# the catchment latitude and longitude as our CaSPAr data has been averaged at the catchment scale.
# We also need to tell the model to deaccumulate the precipitation and shift it in time by 6 hours for our
# catchment (UTC timezones):
data_kwds = {
"ALL": {
"elevation": hru["elevation"],
"Latitude": hru["latitude"],
"Longitude": hru["longitude"],
},
"PRECIP": {
"Deaccumulate": True,
"TimeShift": -0.25,
"LinearTransform": {
"scale": 1.0
}, # Since we are deaccumulating, we need to manually specify scale.
}, # We are already in mm, so leave it like so (scale = 1.0).
"TEMP_AVE": {
"TimeShift": -0.25,
},
}
# Handle CFTime encoding
time_coder = xr.coders.CFDatetimeCoder(use_cftime=True)
# ECCC forecast time format is a bit complex to work with, so we will use cftime to make it more manageable.
fcst_tmp = xr.open_dataset(fname, decode_times=time_coder)
# Get the first timestep that will be used for the model simulation
start_date = fcst_tmp.time.data[0] + dt.timedelta(days=1)
# Model configuration for forecasting, including correct start date and forecast duration and initial state
model_config_fcst = emulators.GR4JCN(
params=[0.529, -3.396, 407.29, 1.072, 16.9, 0.947],
Gauge=[
rc.Gauge.from_nc(
fname, data_type=data_type, alt_names=alt_names, data_kwds=data_kwds
)
],
HRUs=[hru],
StartDate=start_date,
Duration=duration,
RunName="Realtime_forecast_NB",
).set_solution(hotstart, timestamp=False)
# Generate the forecast by providing all necessary information to generate virtual stations representing
# the forecast members. Note that we are using the hindcasting tools, because there is effectively no difference
# between operational hindcasting and operational forecasting except for the forecast issue time and data
# availability, which we solved by using the most recent ECCC forecasts with a warmed-up model and hotstart file.
forecast_sims = forecasting.hindcast_from_meteo_forecast(
model_config_fcst,
forecast=fname,
ens_dim="members",
# We also need to provide the necessary information to create gauges inside the forecasting model:
data_kwds=data_kwds,
data_type=data_type,
alt_names=alt_names,
)
display(forecast_sims.hydrograph)
And, for visual representation of the forecasts:
# Simulate an observed streamflow timeseries: Here we take a member from the ensemble, but you should use your own
# observed timeseries:
qq = forecast_sims.hydrograph.q_sim[0, :, 0]
# This is to be replaced with a call to the forecast graphing WPS as soon as merged.
# model.q_sim.plot.line("b", x="time")
forecast_sims.hydrograph.q_sim[:, :, 0].plot.line("b", x="time", add_legend=False)
forecast_sims.hydrograph.q_sim[1, :, 0].plot.line("b", x="time", label="forecasts")
qq.plot.line("r", x="time", label="observations")
plt.legend(loc="upper left")
plt.show()