Snow#
easysnowdata.snow#
Snow: classifications, masks, cover and snow water equivalent.
Static products#
snow_classificationSturm & Liston seasonal snow classes (NSIDC-0768, hosted COG).
mountain_snow_maskWrzesien mountain snow mask and its clouds layer (Zenodo).
Time series#
modis,viirsMODIS and VIIRS snow cover.
snodasSNODAS snow water equivalent and snow depth.
ucla_srUCLA 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-hdf4on conda-forge).planetary-computermodis-10A1-061/modis-10A2-061as 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")
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 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
.tarper 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-engineClimate 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")
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.
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_SRv1, water years 1985–2021 over the western United States.hmaHMA_SR_Dv1, 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
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 for VIIRS snow granules and return them as a |
|
Load VIIRS snow cover as a lazy |
|
Corner coordinates and shape from an HDF-EOS5 |