Skip to content

Commit 69141bb

Browse files
IoannisSifnaiosAdamRJensenkandersolar
authored
Add get_surfrad iotools function (#2836)
* generate get_surfrad * function documentation * fix linter * linter v2 * add tests * fix test linter * minor error * linter again * Update docs/sphinx/source/whatsnew/v0.15.3.rst Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> * Update pvlib/iotools/surfrad.py Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> * Update tests/iotools/test_surfrad.py Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> * Update test_surfrad.py * Update pvlib/iotools/surfrad.py Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com> --------- Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> Co-authored-by: Kevin Anderson <kevin.anderso@gmail.com>
1 parent ed86cb3 commit 69141bb

5 files changed

Lines changed: 149 additions & 1 deletion

File tree

docs/sphinx/source/reference/iotools.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ A solar radiation network in the USA, run by NOAA.
176176
:toctree: generated/
177177

178178
iotools.read_surfrad
179+
iotools.get_surfrad
179180

180181

181182
MIDC

docs/sphinx/source/whatsnew/v0.15.3.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ Enhancements
5050
:py:func:`~pvlib.iotools.get_nsrdb_psm4_polar_tmy`.
5151
(:issue:`2639`, :pull:`2807`)
5252
* Ensure all timezones are available in all OSs. (:issue:`2795`, :pull:`2809`)
53+
* Add :py:func:`pvlib.iotools.get_surfrad` for retrieving irradiance data
54+
from NOAA's SURFRAD network.
55+
(:issue:`1155`, :pull:`2836`)
5356
* Implement the ANTS-2D bifacial irradiance model in
5457
:py:func:`pvlib.bifacial.ants2d.get_irradiance`. (:pull:`2740`)
5558
* Add parameters ``g0`` and ``g1`` to allow segmented ground surfaces in
@@ -109,3 +112,4 @@ Contributors
109112
* Jason Lun Leung (:ghuser:`jason-rpkt`)
110113
* Leonardo Scappatura (:ghuser:`Leonard013`)
111114
* Carolina Crespo (:ghuser:`cbcrespo`)
115+
* Ioannis Sifnaios (:ghuser:`IoannisSifnaios`)

pvlib/iotools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pvlib.iotools.srml import read_srml # noqa: F401
44
from pvlib.iotools.srml import get_srml # noqa: F401
55
from pvlib.iotools.surfrad import read_surfrad # noqa: F401
6+
from pvlib.iotools.surfrad import get_surfrad # noqa: F401
67
from pvlib.iotools.midc import read_midc # noqa: F401
78
from pvlib.iotools.midc import read_midc_raw_data_from_nrel # noqa: F401
89
from pvlib.iotools.crn import read_crn # noqa: F401

pvlib/iotools/surfrad.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from urllib.request import urlopen, Request
66
import pandas as pd
77
import numpy as np
8+
import warnings
9+
import urllib.error
10+
811

912
SURFRAD_COLUMNS = [
1013
'year', 'jday', 'month', 'day', 'hour', 'minute', 'dt', 'zen',
@@ -181,3 +184,99 @@ def _format_index(data):
181184
data.index = index
182185
data = data.tz_localize('UTC')
183186
return data
187+
188+
189+
def get_surfrad(station, start, end, map_variables=True,
190+
url="https://gml.noaa.gov/aftp/data/radiation/surfrad/"):
191+
"""
192+
Request data from NOAA SURFRAD and read it into a DataFrame.
193+
194+
The SURFRAD network is described in [1]_. The README files are located in
195+
the station directories in the SURFRAD data archives [2]_. In addition to
196+
the FTP server, the SURFRAD files are also available via HTTP access [3]_.
197+
198+
Data is returned for complete days, including ``start`` and ``end``.
199+
200+
Parameters
201+
----------
202+
station : str
203+
Three-letter SURFRAD station abbreviation.
204+
start : datetime-like
205+
First day of the requested period.
206+
end : datetime-like
207+
Last day of the requested period.
208+
map_variables : bool, default True
209+
Passed through to :py:func:`~pvlib.iotools.read_surfrad`:
210+
whether to rename columns to pvlib variable names
211+
(e.g. ``'dw_solar'`` -> ``'ghi'``).
212+
url : str, default 'https://gml.noaa.gov/aftp/data/radiation/surfrad/'
213+
Base URL of the SURFRAD archive.
214+
215+
Returns
216+
-------
217+
data : pd.DataFrame
218+
Dataframe with data from SURFRAD.
219+
meta : dict
220+
Metadata.
221+
222+
See Also
223+
--------
224+
pvlib.iotools.read_surfrad
225+
226+
Notes
227+
-----
228+
Missing days (e.g. before a station's operational start date, or gaps
229+
in the archive) are skipped with a warning rather than raising an error.
230+
231+
Examples
232+
--------
233+
>>> data, meta = pvlib.iotools.get_surfrad(
234+
... station='bon', start='2020-01-01', end='2020-01-31')
235+
236+
References
237+
----------
238+
.. [1] NOAA Earth System Research Laboratory Surface Radiation Budget
239+
Network
240+
`SURFRAD Homepage <https://www.esrl.noaa.gov/gmd/grad/surfrad/>`_
241+
.. [2] NOAA SURFRAD Data Archive
242+
`SURFRAD Archive <ftp://aftp.cmdl.noaa.gov/data/radiation/surfrad/>`_
243+
.. [3] `NOAA SURFRAD HTTP Index
244+
<https://gml.noaa.gov/aftp/data/radiation/surfrad/>`_
245+
"""
246+
start = pd.to_datetime(start)
247+
end = pd.to_datetime(end)
248+
249+
dates = pd.date_range(start.floor('D'), end, freq='D')
250+
station = station.lower()
251+
252+
filenames = [
253+
f"{station}/{d.year}/{station}{d.strftime('%y')}{d.dayofyear:03}.dat" # noqa: E231,E501
254+
for d in dates
255+
]
256+
257+
dfs = []
258+
file_metadata = None
259+
for f in filenames:
260+
try:
261+
dfi, file_metadata = read_surfrad(url + f,
262+
map_variables=VARIABLE_MAP)
263+
dfs.append(dfi)
264+
265+
except urllib.error.HTTPError:
266+
warnings.warn(f"The following file was not found: {f}")
267+
268+
if not dfs:
269+
raise ValueError(
270+
f"No data retrieved for station '{station}' between "
271+
f"{start.date()} and {end.date()}. Check the station code "
272+
"and date range."
273+
)
274+
275+
data = pd.concat(dfs, axis='rows')
276+
meta = {
277+
'station': station,
278+
'filenames': filenames,
279+
# all files should share metadata, so just take it from the last one
280+
**file_metadata,
281+
}
282+
return data, meta

tests/iotools/test_surfrad.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
import pytest
33

44
from pvlib.iotools import surfrad
5-
from tests.conftest import TESTS_DATA_DIR, RERUNS, RERUNS_DELAY
5+
from tests.conftest import (
6+
TESTS_DATA_DIR,
7+
assert_frame_equal,
8+
RERUNS,
9+
RERUNS_DELAY,
10+
)
611

712
testfile = TESTS_DATA_DIR / 'surfrad-slv16001.dat'
813
network_testfile = ('ftp://aftp.cmdl.noaa.gov/data/radiation/surfrad/'
@@ -73,3 +78,41 @@ def test_read_surfrad_metadata():
7378
'tz': 'UTC'}
7479
_, metadata = surfrad.read_surfrad(testfile)
7580
assert metadata == expected
81+
82+
83+
@pytest.mark.remote_data
84+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
85+
def test_get_surfrad():
86+
df, meta = surfrad.get_surfrad('slv', '2016-01-01', '2016-01-01')
87+
88+
assert meta['station'] == 'slv'
89+
assert isinstance(meta['filenames'], list)
90+
91+
assert len(df) == 1440
92+
assert df.index[0] == pd.to_datetime('2016-01-01 00:00+00:00')
93+
assert df.index[-1] == pd.to_datetime('2016-01-01 23:59+00:00')
94+
95+
expected, _ = surfrad.read_surfrad(testfile)
96+
assert_frame_equal(df, expected)
97+
98+
99+
@pytest.mark.remote_data
100+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
101+
def test_get_surfrad_missing_day():
102+
# SURFRAD's Alamosa station data begins 2014-07-28 (slv14209.dat), so
103+
# requesting the day before that will raise a warning
104+
message = 'The following file was not found: slv/2014/slv14208.dat'
105+
with pytest.warns(UserWarning, match=message):
106+
df, meta = surfrad.get_surfrad('slv', '2014-07-27', '2014-07-28')
107+
108+
# but the data for 2014-07-28 is still returned
109+
assert not df.empty
110+
111+
112+
@pytest.mark.remote_data
113+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
114+
def test_get_surfrad_no_data():
115+
message = "No data retrieved for station 'xxx'"
116+
with pytest.warns(UserWarning):
117+
with pytest.raises(ValueError, match=message):
118+
surfrad.get_surfrad('xxx', '2016-01-01', '2016-01-01')

0 commit comments

Comments
 (0)