California snow pillows and courses (CDEC)#

The California Data Exchange Center serves the California Cooperative Snow Surveys: automated snow pillows that report SWE, snow depth, precipitation and temperature daily (hourly at many), and manual snow courses surveyed around the first of the month through winter and spring. Both are in the inventory. Only the pillows have a daily record, so daily_only keeps the pillows.

There is one route, source="cdec": CDEC’s JSON data servlet for the values and its station pages for the metadata. No account is needed. Station codes are CDEC’s three letters (BLK), the same in the global code.

The figures show the stations of the central Sierra Nevada coloured by kind, one water year of SWE at the highest pillows in the box, and three contrasting water years at one pillow on a single October-to-September axis.

import easysnowdata as esd

aoi = (-120.6, 38.4, -119.6, 39.4)

box = esd.parse_aoi(aoi)
print(box, box.utm_crs)  # the map below is drawn in the AOI's UTM zone
AOI(bounds=(-120.6000, 38.4000, -119.6000, 39.4000), clip=True) EPSG:32610

Where the product comes from.

for src in esd.catalog.get("cdec-stations").sources:
    print(f"{src.id:20} {src.title:45} {', '.join(src.requires) or 'no account'}")
cdec                 California Data Exchange Center               no account

Everything CDEC has in the box, and the pillows among them. The archive inventory’s daily_or_better is the pipeline’s verdict after actually retrieving daily values, not what CDEC’s sensor list advertises.

everything_gdf = esd.stations.inventory(aoi, networks="cdec")
everything_gdf["kind"] = everything_gdf["daily_or_better"].map(
    {True: "snow pillow (daily)", False: "snow course"}
)
print(everything_gdf["kind"].value_counts().to_string())
kind
snow course            77
snow pillow (daily)    74

Pillows and courses share the same basins; the courses are the older network and many pillows were installed beside an existing course.

ax = esd.plotting.points(
    everything_gdf.to_crs(box.utm_crs),
    column="kind",
    legend_label="CDEC site kind",
    title="CDEC stations, central Sierra Nevada",
)
CDEC stations, central Sierra Nevada

One water year of SWE at the five highest pillows. CDEC carries two SWE sensors, the raw pillow (3) and the adjusted one (82); the client prefers the adjusted sensor and records what it used in native_variables.

daily_gdf = everything_gdf[everything_gdf["daily_or_better"].fillna(False).astype(bool)]
highest_gdf = daily_gdf.nlargest(5, "elevation_m")
obs_ds = esd.stations.load(highest_gdf, variables="swe", time="2023-10/2024-09")
print("native CDEC sensors behind `swe`:", obs_ds["swe"].attrs["native_variables"])

ax = esd.plotting.timeseries(obs_ds["swe"], title="Water year 2024")
Water year 2024
native CDEC sensors behind `swe`: SNO ADJ

Two of the five stop reporting in January. “Daily-verified” means the probe retrieved daily values from the station, not that its record is gapless; count the observations before trusting a seasonal statistic.

print(obs_ds["swe"].count(dim="time").to_series().to_string())
station
MSK    365
HVN    366
SCN     49
EBB    366
HHM     40

Blue Lakes (2435 m) has reported since 1980. Three water years side by side: 2015, the record-low snowpack; 2017 and 2023, two of the largest on record.

blue_lakes_ds = esd.stations.load("BLK", variables="swe", time="2014-10/2023-09")
swe_da = blue_lakes_ds["swe"]
picked_da = swe_da.where(swe_da["water_year"].isin([2015, 2017, 2023]), drop=True)
ax = esd.plotting.timeseries(
    picked_da,
    by_water_year=True,
    title=f"{str(swe_da['name'].values[0]).title()}: a lean winter and two big ones",
)
Blue Lakes: a lean winter and two big ones

In the lean year SWE never passed 25 cm and the pillow was bare by early April. The big years did not just pile up more: their peaks came a month later, in late April and May, and melt ran into July.

peak_da = swe_da.groupby("water_year").max()
print(peak_da.to_series().round(0).to_string())
station  water_year
BLK      2015           25.0
         2016           86.0
         2017          161.0
         2018           52.0
         2019          138.0
         2020           71.0
         2021           55.0
         2022           49.0
         2023          189.0

Total running time of the script: (0 minutes 6.697 seconds)