NREL products & behaviour — TMY and bbox point grids (live)¶
The quickstart covered the default hourly NSRDB
product and the WIND Toolkit. This notebook demonstrates two further
capabilities of the nrel backend:
- the Typical Meteorological Year product (
nsrdb-tmy), and - sampling a bounding box to a point grid (one keyed call per
(point, year), throttled to the NREL rate limit).
It also explains why the backend rejects an aggregate= argument.
import os
import tempfile
import pandas as pd
from earthlens.earthlens import EarthLens
from earthlens.nrel import Catalog
API_KEY = os.environ["NREL_API_KEY"]
EMAIL = os.environ["NREL_EMAIL"]
OUT = tempfile.mkdtemp()
The catalog: three products¶
Catalog().available() lists the curated products; each row carries the source
archive, the CSV endpoint, the default attributes, and the value columns.
catalog = Catalog()
print(catalog.available())
{pid: catalog.get(pid).source for pid in catalog.available()}
['nsrdb-psm3', 'nsrdb-tmy', 'wtk']
{'nsrdb-psm3': 'nsrdb', 'nsrdb-tmy': 'nsrdb', 'wtk': 'wtk'}
Typical Meteorological Year (nsrdb-tmy)¶
Select the TMY product with product="nsrdb-tmy". TMY is a single synthetic
year, so the start/end window is collapsed to one names=tmy call and the
year column is tagged "tmy".
tmy = EarthLens(
data_source="nrel",
product="nsrdb-tmy",
variables=["ghi", "dni", "air_temperature"],
start="2020-01-01",
end="2020-12-31",
point=(39.74, -105.18),
api_key=API_KEY,
email=EMAIL,
path=OUT,
).download(progress_bar=False)
print(tmy.shape, "| year tag:", tmy["year"].unique().tolist())
tmy.head()
2026-06-28 16:05:10.166 | INFO | earthlens.nrel.backend:download:384 - NREL nsrdb-tmy: 8760 row(s) -> C:\Users\main\AppData\Local\Temp\tmphcbm_svm\nrel_nsrdb-tmy.csv
2026-06-28 16:05:10.167 | INFO | earthlens.nrel.backend:download:385 - Data from the NREL National Solar Radiation Database (NSRDB) / WIND Toolkit via the NREL Developer Network (developer.nlr.gov). Please cite NREL per https://nsrdb.nrel.gov/ and the WIND Toolkit references.
(8760, 8) | year tag: ['tmy']
| time | GHI | DNI | Temperature | lat | lon | year | product | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2011-01-01 00:30:00 | 0 | 0 | -16.0 | 39.74 | -105.18 | tmy | nsrdb-tmy |
| 1 | 2011-01-01 01:30:00 | 0 | 0 | -16.0 | 39.74 | -105.18 | tmy | nsrdb-tmy |
| 2 | 2011-01-01 02:30:00 | 0 | 0 | -15.9 | 39.74 | -105.18 | tmy | nsrdb-tmy |
| 3 | 2011-01-01 03:30:00 | 0 | 0 | -15.8 | 39.74 | -105.18 | tmy | nsrdb-tmy |
| 4 | 2011-01-01 04:30:00 | 0 | 0 | -15.7 | 39.74 | -105.18 | tmy | nsrdb-tmy |
A bounding box becomes a point grid¶
Pass lat_lim / lon_lim (instead of point=) and the box is sampled at
spacing_deg; each grid point is one keyed call per year. Here a 0.5 degree
box at 0.5 degree spacing yields a 2x2 grid (4 points) x 1 year = 4 throttled
calls. The returned frame stacks one block of hourly rows per point.
grid = EarthLens(
data_source="nrel",
variables=["ghi"],
start="2020-01-01",
end="2020-12-31",
lat_lim=[39.5, 40.0],
lon_lim=[-105.5, -105.0],
spacing_deg=0.5,
api_key=API_KEY,
email=EMAIL,
path=OUT,
).download(progress_bar=False)
print("rows:", len(grid))
grid.groupby(["lat", "lon"]).size().rename("hours")
2026-06-28 16:05:19.960 | INFO | earthlens.nrel.backend:download:384 - NREL nsrdb-psm3: 35040 row(s) -> C:\Users\main\AppData\Local\Temp\tmphcbm_svm\nrel_nsrdb-psm3.csv
2026-06-28 16:05:19.961 | INFO | earthlens.nrel.backend:download:385 - Data from the NREL National Solar Radiation Database (NSRDB) / WIND Toolkit via the NREL Developer Network (developer.nlr.gov). Please cite NREL per https://nsrdb.nrel.gov/ and the WIND Toolkit references.
rows: 35040
lat lon
39.5 -105.5 8760
-105.0 8760
40.0 -105.5 8760
-105.0 8760
Name: hours, dtype: int64
A large box at a fine spacing_deg, or a wide year range, fans out into many
calls — the backend throttles to <=1 request/second (5000/day) and raises a
ValueError past max_requests (default 500), so a country-scale request never
silently fires thousands of calls. Coarsen spacing_deg or narrow the years to
keep the count down.
Why aggregate= is rejected¶
nrel is a tabular backend: it already returns the resolved hourly / TMY
series, so there is no gridded raster to reduce. The EarthLens facade rejects
a non-None aggregate= for a tabular backend with NotImplementedError. To
get a coarser cadence, resample the returned frame in pandas instead:
daily_mean_ghi = (
grid[grid["lon"] == grid["lon"].iloc[0]]
.set_index("time")["GHI"]
.resample("1D")
.mean()
)
daily_mean_ghi.head()
time 2020-01-01 97.833333 2020-01-02 121.187500 2020-01-03 113.125000 2020-01-04 106.958333 2020-01-05 117.875000 Freq: D, Name: GHI, dtype: float64
Takeaway¶
The same download() shape serves the hourly, TMY, and gridded cases: pick the
product with product= (or the nsrdb / wind-toolkit aliases), pick the
attributes with variables=, and pass either a point= or a bbox. The result
is always a long-format pandas.DataFrame you resample or pivot in pandas.