Snow#

easysnowdata.snow#

Snow: classifications, masks, cover and snow water equivalent.

Static products#

snow_classification

Sturm & Liston seasonal snow classes (NSIDC-0768, hosted COG).

mountain_snow_mask

Wrzesien mountain snow mask and its clouds layer (Zenodo).

Time series#

modis, viirs

MODIS and VIIRS snow cover.

snodas

SNODAS snow water equivalent and snow depth.

ucla_sr

UCLA snow reanalysis.

Class tables, binary-snow thresholding and the SNODAS flat-file reader are pure functions in easysnowdata.processing.snow.

import easysnowdata as esd

aoi = (-121.94, 46.72, -121.54, 46.99)
esd.snow.snow_classification.load(aoi, source="hosted-cog")
esd.snow.mountain_snow_mask.load(aoi)
esd.snow.snodas.load(aoi, time="2024-03")

easysnowdata.snow.modis#

MODIS snow cover: MOD10A1, MOD10A2 and the cloud-gap-filled MOD10A1F.

Sources (companion file §B.4):

nsidc (default)

NSIDC via earthaccess: the archive of record and the only route that carries the cloud-gap-filled product and the Aqua siblings. The granules are HDF-EOS2 (HDF4), which cannot be read through fsspec file objects, so they are downloaded once into the easysnowdata cache and opened as GDAL subdatasets. Needs Earthdata Login and a GDAL with the HDF4 driver (libgdal-hdf4 on conda-forge).

planetary-computer

modis-10A1-061 / modis-10A2-061 as COGs, credential-free, kept as the historical route. Archiving of MOD10A2 there reportedly stopped in 2025, which is an access-route failure rather than a product failure: the same granules are still at NSIDC (verified 2026-09-16).

import easysnowdata as esd
granules = esd.snow.modis.search(aoi, "2023-03", product="MOD10A1F")
snow = esd.snow.modis.load(aoi, "2023-03", product="MOD10A1F")
binary = esd.processing.binary_snow(snow["CGF_NDSI_Snow_Cover"], product="MOD10A1F")

search

Search for MODIS snow granules and return them as a GeoDataFrame.

load

Load MODIS snow cover as a lazy xarray.Dataset (time, y, x).

easysnowdata.snow.mountain_snow_mask#

Wrzesien global seasonal mountain snow mask (§4.3).

import easysnowdata as esd

aoi = (-121.94, 46.72, -121.54, 46.99)
mask = esd.snow.mountain_snow_mask.load(aoi)                   # mountains
snow = esd.snow.mountain_snow_mask.load(aoi, layer="snow")     # all terrain
cloud = esd.snow.mountain_snow_mask.load(aoi, layer="clouds")  # indeterminacy
esd.plotting.categorical(mask)

MODIS MOD10A2 snow-cover extent and the GTOPO30 elevation model, classified at 30 arcsec into little-to-no, ephemeral and seasonal snow, with mountains separated from all terrain (Wrzesien et al. 2019).

The archive is one zipped GeoTIFF per layer on Zenodo. Each is fetched once into the easysnowdata cache (~14-26 MB) instead of being re-read over HTTPS on every call, which is what made the old function slow; cache=False restores the direct zip+https:// read.

Upstream fill values. The two class rasters are written as 32-bit with nodata 256 (and 265 in places) rather than 255, so unmasked they arrive as uint32. easysnowdata.processing.snow.repair_fill_values() puts every value above the last class back to 255 and returns uint8; the loader applies it.

load

Load one layer of the mountain snow mask for aoi.

easysnowdata.snow.snodas#

SNODAS: NOHRSC daily 1 km snow water equivalent and snow depth.

Sources (companion file §B.5):

nsidc (default, new)

The authoritative NSIDC G02158 archive: one .tar per day holding gzipped flat-binary grids with their own text headers. No account, no Earth Engine — the cost is a small download per day and a reader (easysnowdata.processing.snow) instead of an analysis-ready cube.

gee-climate-engine

Climate Engine’s community re-hosting on Earth Engine: fast, lazy and server-side subset, about a day behind, but a non-authoritative mirror behind Earth Engine credentials.

Both should agree; the gallery example checks that they do.

Glaciers saturate the grid. SNODAS never melts perennial ice out, so SWE grows without bound over glaciers and eventually saturates the 16-bit field at 32767 mm (32.767 m). Verified 2026-09-16 on 2024-03-15: eight CONUS pixels are saturated and they sit on Mount Rainier’s and Mount Baker’s summit ice caps, while the Rainier box’s median is 0.86 m. Those values are the model’s, not a reader artefact, so they are passed through untouched; mask them yourself (ds["SWE"].where(ds["SWE"] < 30)) when a basin contains glaciers.

import easysnowdata as esd
swe = esd.snow.snodas.load(aoi, "2024-03-01/2024-03-07")            # NSIDC
swe = esd.snow.snodas.load(aoi, "2024-03", source="gee-climate-engine")

tar_url

The NSIDC URL of one SNODAS day (…/2024/03_Mar/SNODAS_20240315.tar).

search

List the SNODAS days a request covers, with their archive URLs.

load

Load SNODAS as an xarray.Dataset (time, latitude, longitude).

easysnowdata.snow.snow_classification#

Sturm & Liston global seasonal snow classification (§4.3, companion §B.6).

import easysnowdata as esd

aoi = (-121.94, 46.72, -121.54, 46.99)
snow_class = esd.snow.snow_classification.load(aoi)                     # NSIDC-0768
snow_class = esd.snow.snow_classification.load(aoi, source="hosted-cog")  # no login
esd.plotting.categorical(snow_class)

Six seasonal snow classes (tundra, boreal forest, maritime, ephemeral, prairie, montane forest) plus ice and ocean, from air-temperature, precipitation and wind-speed climatologies.

Sources. NSIDC-0768 is authoritative and is the default (§12 Q8); its HTTPS directory redirects to Earthdata Login, so the file is fetched once into the easysnowdata cache with an authenticated session. source="hosted-cog" needs no credentials and reads the 10 arcsec global map as a COG with range requests — that location is likely to change (the account hosting it is funded to 2026-10-26), which is why it is one entry here.

filename

The NSIDC-0768 file name for a resolution and region (GL or NA).

load

Load the seasonal snow classification for aoi.

easysnowdata.snow.ucla_sr#

UCLA snow reanalysis: Western US (WUS_UCLA_SR) and High Mountain Asia (HMA_SR_D).

Daily posterior SWE, snow depth and snow-covered area at 480 m from a particle-batch-smoother reanalysis, one NetCDF-4 granule per water year and 1°×1° tile. Two regions, the same layout:

wus (default)

WUS_UCLA_SR v1, water years 1985–2021 over the western United States.

hma

HMA_SR_D v1, the High Mountain Asia sibling — new here (plan §4.3).

Both come from NSIDC through earthaccess and need Earthdata Login.

Glaciers accumulate without bound, the same artefact SNODAS has (easysnowdata.snow.snodas): the reanalysis never melts perennial ice out, so SWE over an ice cap grows year on year. Verified 2026-09-17 over the Mount Rainier box for January 2020 — the median is 0.14 m and the 90th percentile 1.09 m, both seasonally sensible, while 135 of 5 642 pixels exceed 10 m and the maximum reaches 105.9 m on the summit ice cap. Those are the model’s values, passed through untouched; mask them (da.where(da < 10)) before taking a basin maximum where there are glaciers.

Virtualization (plan §4.9)#

These granules are NetCDF-4 without DMR++ sidecars, so earthaccess.virtualize() falls back to scanning each file’s HDF5 metadata once and caching kerchunk references. That is worth it for a long series and wasted on one water year, which is what virtualize="auto" encodes: it turns on past VIRTUALIZE_THRESHOLD granules. True/False force it. The reference cache lives under easysnowdata.config.cache_dir(), so a second call over the same granules skips the scan.

import easysnowdata as esd
swe = esd.snow.ucla_sr.load(aoi, "2019-10/2021-09")                  # WUS
swe = esd.snow.ucla_sr.load(aoi, "2000-10/2001-09", region="hma")    # HMA

search

Search the reanalysis granules and return them as a GeoDataFrame.

load

Load the reanalysis as a lazy xarray.DataArray (time, y, x).

easysnowdata.snow.viirs#

VIIRS snow cover: VNP10A1 and the cloud-gap-filled VNP10A1F (375 m).

New in the rewrite and Eric’s first priority (plan §12 Q11): VIIRS is the successor to MODIS snow cover as Terra winds down, at 375 m instead of 500 m and with the same NDSI byte convention, so easysnowdata.processing.binary_snow() works on both.

One source: NSIDC via earthaccess (NSIDC_CPRD, cloud-hosted). The granules are HDF-EOS5 (.h5), which — unlike the HDF4 MODIS granules — GDAL and h5netcdf can both read, so they are opened through the HDF5 driver after a download to the cache. The grid comes from StructMetadata.0, which carries the sinusoidal corner coordinates.

import easysnowdata as esd
granules = esd.snow.viirs.search(aoi, "2023-03", product="VNP10A1F")
snow = esd.snow.viirs.load(aoi, "2023-03", product="VNP10A1F")
binary = esd.processing.binary_snow(snow["CGF_NDSI_Snow_Cover"], product="VNP10A1F")

search

Search for VIIRS snow granules and return them as a GeoDataFrame.

load

Load VIIRS snow cover as a lazy xarray.Dataset (time, y, x).

read_struct_metadata

Corner coordinates and shape from an HDF-EOS5 StructMetadata.0.