Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Analyzing a Cloud-Native IVT Zarr for Atmospheric Rivers

Authors
Affiliations
CUAHSI - Consortium of Universities for the Advancement of Hydrologic Science, Inc.
CUAHSI - Consortium of Universities for the Advancement of Hydrologic Science, Inc.
CUAHSI - Consortium of Universities for the Advancement of Hydrologic Science, Inc.
CUAHSI - Consortium of Universities for the Advancement of Hydrologic Science, Inc.

Description

This notebook shows how to authenticate with HydroShare and stream a cloud-native Zarr (~40 GB) of Integrated Vapor Transport (IVT) derived from the CESM2 Large Ensemble (LENS2). The full collection (~1.9 PB, NetCDF) is hosted by the NSF Research Data Archive (dataset d651056). In August 2024, we extracted a ~40 GB IVT subset from the legacy NCAR archive and republished it on HydroShare in Zarr format. Using HydroShare’s S3-compatible endpoint with xarray+dask, we perform time/space subsetting and detect atmospheric rivers (ARs) with the common IVT ≥ 250 kg/m/s  threshold, then assign AR categories based on joint intensity and duration. We also compute an “AR degree” metric, defined as event-integrated IVT (the sum of IVT over an event’s duration; IVT × days), as a simple indicator of event severity.

Software Requirements

This notebook has been tested using Python v3.11.8 using the following library versions:

  • dask>=2025.7.0

  • xarray>=2024.2.0

  • geopandas>=1.1.1

  • h5netcdf>=1.6.4

  • hvplot>=0.11.3

  • requests>=2.32.2

  • rioxarray>=0.19.0

  • s3fs>=2025.5.1

  • numpy>=2.3.1

  • cftime>=1.6.4

  • folium>=0.20.0

Environment Setup: Imports & Dask (Cluster/Client)

Loading...
Loading...
Loading...
Loading...
C:\Users\AbnerBogan\anaconda3\envs\ciroh-devcon-2026-workshop-hydroinformatics\Lib\site-packages\rdflib\plugin.py:111: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  from pkg_resources import iter_entry_points
h5netcdf installed: True
h5py installed: True
C:\Users\AbnerBogan\anaconda3\envs\ciroh-devcon-2026-workshop-hydroinformatics\Lib\site-packages\distributed\node.py:188: UserWarning: Port 8787 is already in use.
Perhaps you already have a cluster running?
Hosting the HTTP server on port 59308 instead
  warnings.warn(
http://127.0.0.1:59308/status
<dask.config.set at 0x29c58e381d0>

Authenticate to HydroShare & Configure S3FS

The following cells first obtain S3 credentials from the HydroShare API, initialize s3fs for the HydroShare S3 endpoint, and find the exact bucket and key prefix for the resource, then combine them to build s3_path for listin/reading the Zarr store.

The Zarr store contained in the HydroShare resource below is part of a larger collection of CESM2 LENS model outputs on AWS maintained by NCAR.

Authenticate your HydroShare account and identify the HydroShare resource containing the IVT data:

Enter your password:  ········

Access the HydroShare API endpoint to create and retrieve temporary S3 credentials for an authenticated HydroShare user:

Run the next cell to create a connection to the HydroShare S3 bucket using the s3fs library. s3fs provides a filesystem-like interface to S3, making it easy to list, read, and write files as if they were on your local machine. For more information, refer to the s3fs documentation.

['igarousi/53effa6f532b43a5972daa5336932224/data/contents/ivt.zarr']

For pushing files back to HydroShare later in this notebook, we can also authenticate with HydroShare directly with the hsclient library. Here, you can also define your HydroShare user object by going to your profile in HydroShare and extracting the user ID from the URL of your profile (e.g., for Abner Bogan’s profile https://www.hydroshare.org/user/24566/, his user ID is 24566).

Open the Integrated Vapor Transport (IVT) Zarr Store Lazily

A Zarr store is not a single file, but actually:

  • a directory-like collection

  • contains many chunk files + metadata

  • more like a smart reference system describing where the data live and how to compute with them later Zarr Schematic
    Max Jones, Development Seed

As we will see, the IVT Zarr store is large. Before we retrieve the numerical data arrays, we use a method called “lazy loading” to read only the metadata, coordinates, and structure by using xarray and dask. After doing this the data are still sitting remotely in HydroShare. Lazy loading avoids unnecessary downloading of large quantites of data while still accessing the information we need to subset the dataset at a location and time period of interest.

<class 'dask.array.core.Array'>

The Zarr dataset behaves like a directory tree of chunk files and metadata objects stored in cloud object storage.
Inspect the Mapper Object directly to see some of the contents of the Zarr store:

['.zattrs', '.zgroup', '.zmetadata', 'IVT/.zarray', 'IVT/.zattrs', 'IVT/0.0.0', 'IVT/1.0.0', 'IVT/10.0.0', 'IVT/100.0.0', 'IVT/101.0.0', 'IVT/102.0.0', 'IVT/103.0.0', 'IVT/104.0.0', 'IVT/105.0.0', 'IVT/106.0.0', 'IVT/107.0.0', 'IVT/108.0.0', 'IVT/109.0.0', 'IVT/11.0.0', 'IVT/110.0.0']

Inspect the xarray.Dataset to see the information we can access with Lazy Loading:

Loading...

Expanding the icon for Data variables:IVT above reveals a table with important information about the Zarr store and the Dask chunking. This summary table shows that a ~38 GB multidimensional dataset is being represented lazily as 367 independently accessible ~105 MB chunks. This allows Dask to stream only the needed pieces instead of loading the entire dataset into memory.

Check the IVT dataset chunk sizes.
→ This zarr store is partitoned only temporally, not spatially. This means that each chunk contains 1000 timesteps for the FULL model domain.

Frozen({'time': (1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 462), 'lat': (192,), 'lon': (288,)})

Check file size. Zarr chunks are compressed on disk. HydroShare shows the compressed total (e.g., 24 GiB). ds.nbytes reports the uncompressed size of all arrays in the dataset, i.e., what it would take in RAM if loaded.

Variable size: 40.5 GB
Number of years in one file: 251

The time coordinate in this dataset is not strictly monotonic increasing, which can break label-based slicing, resampling, and merges. First, check it with the custom function time_order_report() and then fix the order:

time: monotonic↑=False, monotonic↓=False, unique=False, length=366462
CFTimeIndex([1970-01-01 00:00:00, 1970-01-01 06:00:00, 1970-01-01 12:00:00, 1970-01-01 18:00:00, 1970-01-02 00:00:00, 1970-01-02 06:00:00, 1970-01-02 12:00:00, 1970-01-02 18:00:00, 1970-01-03 00:00:00, 1970-01-03 06:00:00, ... 2100-12-29 18:00:00, 2100-12-30 00:00:00, 2100-12-30 06:00:00, 2100-12-30 12:00:00, 2100-12-30 18:00:00, 2100-12-31 00:00:00, 2100-12-31 06:00:00, 2100-12-31 12:00:00, 2100-12-31 18:00:00, 2101-01-01 00:00:00], dtype='object', length=366462, calendar='noleap', freq=None)
time: monotonic↑=True, monotonic↓=False, unique=False, length=366462
Start: 1850-01-01 00:00:00
End:   2101-01-01 00:00:00

Also, the time axis is a CFTimeIndex, i.e., it uses cftime date types for non-Gregorian calendars (e.g., noleap, 360_day). Many plotting and pandas/xarray functions work best with standard NumPy datetimes, so we convert to proleptic_gregorian (and drop cftime) before plotting:

Note: This conversion will drop timestamps that do not exist in the Gregorian calendar (e.g., Feb 29 in noleap).

Subset to a Location and Period of Interest

Display the full model domain

Before we subset the IVT data for a location, let’s take a look at the spatial area contained in the Zarr store (hint: it’s very large!)

IVT
{'long_name': 'Total (vertically integrated) vapor transport', 'units': 'kg/m/s'}

Plot the full domain extent of the IVT data for a single timestep. Here, we are using the Python package cartopy and accessing a coastline shapefile (ax.coastlines()) from the Natural Earth public domain dataset repository.

<Figure size 1200x800 with 2 Axes>

The cartopy download warning appears the first time you download the coastline shapefiles from Natural Earth because these files are retrieved on demand rather than bundled locally with the Python package.

This is conceptually similar to the cloud-native Zarr workflow in this notebook: xarray + Dask access only the specific IVT Zarr metadata and data chunks needed for the current computation or visualization, rather than downloading the full dataset upfront.

Subset IVT for location and time of interest

Next, we can subset the IVT for a given location and time period. By default we have selected Bodega Bay (38.3332° N, 123.0481° W), a region frequently impacted by wintertime atmospheric rivers—major drivers of seasonal precipitation and flooding across the Pacific Northwest and Northern California (see https://cw3e.ucsd.edu/heavy-rain-in-the-pacific-northwest/ for more info). In this notebook, we focus on 1970–2009, a late-20th-century subset of the CESM2 LENS historical simulations (1850–2014).

Loading...

Similar to the on-demand access of the cartopy visualization above, the folium package used here is generating an interactive Leaflet web map and instructs the browser to request only the needed map tiles without downloading the entire map file locally.

Lazily subset the IVT Zarr store for the time period and point location defined above:

Loading...

Load the subsetted data.
→ This step is where we are actually triggering the retrieval of the selected data chunk into memory!

CPU times: total: 25.3 s
Wall time: 2min 36s

Create a timeseries of IVT at the selected lat/lon point:

Loading...

Select a set of grid cells around the lat/lon point that cover Bodega Bay, CA (38.3332° N, 123.0481° W).

Load the subsetted grid cells (and selected data chunks) into memory:

CPU times: total: 23.5 s
Wall time: 2min 6s

Plot the IVT data for these cells at time=0 to visualize our domain:

<Figure size 640x480 with 3 Axes>
Loading...
Frozen({})

Identify Atmospheric River Events

To identify atmospheric rivers, we first flag periods where IVT ≥ 250 kg/m/s, a common screening threshold for AR conditions (Ralph et al., 2019). We then classify AR strength into Categories 1–5 using the Ralph et al. (2019) scale, which combines peak IVT and event duration. Lower categories indicate weaker ARs, while higher categories indicate extreme/exceptional events. This workflow is implemented in our Python utility module ARUtils.py.

Assign an index to each group of timesteps that meet AR conditions

Compute the duration for each AR event

Compute the category (1-5) for each AR event

Compute the probability of each AR event occurring

Compute the magnitude of each AR event.

Plot Computed AR Category

Mean Atmospheric Rivers category through the record of data. Let’s first build contour plots of mean AR_CATEGORY for each decade in our dataset using cartopy.

<Figure size 1000x800 with 5 Axes>

Build contour plots of mean AR_DEGREE for each decade in our dataset.

<Figure size 1000x800 with 5 Axes>
🎉 Save the Mean Decadal AR Degree Figure for our HydroShare Resource!

Push data back into HydroShare

Now that we have extracted, analyzed and visualized our data of interest, we can push the data back to HydroShare in a new resource through the Python client for the HydroShare API called hsclient.

Create a New Empty Resource

The following code can be used to create a new, empty resource within which you can create content and metadata.It also creates an in-memory object representation of that resource in your local environmment that you can then manipulate with further code.

The HydroShare Identifier for your new resources is: 7c31a443eaf64cb887caf839027a8717
Your new resource is available at: http://www.hydroshare.org/resource/7c31a443eaf64cb887caf839027a8717

Creating and Editing Resource Metadata Elements

We can first add metadata to our resource. Editing metadata elements for a resource can be done in an object oriented way. You can specify all of the metadata elements in code, which will set their values in memory in your local environment. Values of metadata elements can be edited, removed, or replaced by setting them to a new value, appending new values (in the case where the metadata element accepts a list), or by removing the value entirely.

When you are ready to save edits to metadata elements from your local environment to the resource in HydroShare, you can call the save() function on your resource and all of the new metadata values you created/edited will be saved to the resource in HydroShare. Note that by default you are listed as the author and owner of a newly created resource.

The list of Authors includes: 
Bogan, Abner
Resource Title and Abstract

The Title and Abstract metadata elements can be specified as text strings. To modify the Title or Abstract after it has been set, just set them to a different value.

Title: Integrated Vapor Transport - Bodega Bay
Abstract: This resource contains integrated vapor transport (IVT) datafor a selected location and time period. The outputs were generated from a notebook that subsets cloud-hosted IVT data and summarizes changes in atmospheric moisture transport at the specified point.
Subject Keywords

Subject keywords can be specified as a Python list of strings. Keywords can be added by creating a list, appending new keywords to the existing list, or by overriding the existing list with a new one.

The list of keywords for the resource includes:
Integrated vapor transport
Atmospheric Rivers
Hydroinformatics
Spatial and Temporal Coverage

Initially the spatial and temporal coverage for a resource are empty. To set them, you have to create a coverage object of the right type and set the spatial or temporal coverage to that object.

Temporal Coverage:
start=1970-01-01T00:00:00; end=2009-12-31T00:00:00

Spatial Coverage:
name=Bodega Bay; east=-123.125; north=38.0; units=Decimal degrees; projection=WGS 84 EPSG:4326
Funding Agency Credits

Funding agency information contains multiple attributes when you add a funding agency to a HydroShare resource. You can create multiple funding agency entries for a resource, which get stored as a Python list.

Funding sources added: 
Award Title: Cooperative Institute for Research to Operations in HydrologyCooperative Institute for Research to Operations in Hydrology

Adding Files to a Resource

In this section we will add the content files directly to the resource! Note that if you upload files that already exist, those files will be overwritten.

Updated file list after adding a file: 
mean_decadal_ar_degree.png
ivt_timeseries.csv