Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add retrieval functions for daily precip, snow, and temperature data from NOAA RCC ACIS #1767

Merged
merged 19 commits into from Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/sphinx/source/reference/iotools.rst
Expand Up @@ -36,6 +36,7 @@ of sources and file formats relevant to solar energy modeling.
iotools.get_cams
iotools.read_cams
iotools.parse_cams
iotools.get_acis_precipitation

A :py:class:`~pvlib.location.Location` object may be created from metadata
in some files.
Expand Down
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.6.rst
Expand Up @@ -48,6 +48,8 @@ Enhancements
* :py:func:`pvlib.iotools.get_psm3` now uses the new NSRDB 3.2.2 endpoint for
hourly and half-hourly single-year datasets. (:issue:`1591`, :pull:`1736`)
* The default solar position algorithm (NREL SPA) is now 50-100% faster. (:pull:`1748`)
* Added function to retrieve gridded precipitation data from the ACIS service
from NOAA's RCCs: :py:func:`~pvlib.iotools.get_acis_precipitation`. (:issue:`1293`, :pull:`1777`)

Bug fixes
~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions pvlib/iotools/__init__.py
Expand Up @@ -20,3 +20,4 @@
from pvlib.iotools.sodapro import get_cams # noqa: F401
from pvlib.iotools.sodapro import read_cams # noqa: F401
from pvlib.iotools.sodapro import parse_cams # noqa: F401
from pvlib.iotools.acis import get_acis_precipitation # noqa: F401
105 changes: 105 additions & 0 deletions pvlib/iotools/acis.py
@@ -0,0 +1,105 @@
import requests
import pandas as pd
import numpy as np


def get_acis_precipitation(latitude, longitude, start, end, dataset,
kandersolar marked this conversation as resolved.
Show resolved Hide resolved
url="https://data.rcc-acis.org/GridData", **kwargs):
kandersolar marked this conversation as resolved.
Show resolved Hide resolved
"""
Retrieve daily gridded precipitation data from the Applied Climate
kandersolar marked this conversation as resolved.
Show resolved Hide resolved
Information System (ACIS).

The Applied Climate Information System (ACIS) was developed and is
maintained by the NOAA Regional Climate Centers (RCCs) and brings together
climate data from many sources.
AdamRJensen marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
latitude : float
in decimal degrees, between -90 and 90, north is positive
longitude : float
in decimal degrees, between -180 and 180, east is positive
start : datetime-like
First day of the requested period
end : datetime-like
Last day of the requested period
dataset : int
A number indicating which gridded dataset to query. Options include:

* 1: NRCC Interpolated
* 2: Multi-Sensor Precipitation Estimates
* 3: NRCC Hi-Res
* 21: PRISM

See [1]_ for the full list of options.

url : str, default: 'https://data.rcc-acis.org/GridData'
API endpoint URL
kwargs:
Optional parameters passed to ``requests.get``.
kandersolar marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
data : pandas.Series
Daily rainfall [mm]
metadata : dict
Coordinates for the selected grid cell

Raises
------
requests.HTTPError
A message from the ACIS server if the request is rejected

Notes
-----
The returned precipitation values are 24-hour aggregates, but
the aggregation period may not be midnight to midnight in local time.
For example, PRISM data is aggregated from 12:00 to 12:00 UTC,
meaning PRISM data labeled May 26 reflects to the 24 hours ending at
7:00am Eastern Standard Time on May 26.

Examples
--------
>>> prism, metadata = get_acis_precipitation(40.0, -80.0, '2020-01-01',
>>> '2020-12-31', dataset=21)

References
----------
.. [1] `ACIS Web Services <http://www.rcc-acis.org/docs_webservices.html>`_
.. [2] `ACIS Gridded Data <http://www.rcc-acis.org/docs_gridded.html>`_
.. [3] `NRCC <http://www.nrcc.cornell.edu/>`_
.. [4] `Multisensor Precipitation Estimates
<https://www.weather.gov/marfc/Multisensor_Precipitation>`_
.. [5] `PRISM <https://prism.oregonstate.edu/>`_
"""
elems = [
# other variables exist, but are not of interest for PV modeling
{"name": "pcpn", "interval": "dly", "units": "mm"},
]
kandersolar marked this conversation as resolved.
Show resolved Hide resolved
params = {
'loc': f"{longitude},{latitude}",
'sdate': pd.to_datetime(start).strftime('%Y-%m-%d'),
'edate': pd.to_datetime(end).strftime('%Y-%m-%d'),
AdamRJensen marked this conversation as resolved.
Show resolved Hide resolved
'grid': str(dataset),
'elems': elems,
'output': 'json',
'meta': ["ll"], # "elev" should work, but it errors for some databases
AdamRJensen marked this conversation as resolved.
Show resolved Hide resolved
}
response = requests.post(url, json=params,
headers={"Content-Type": "application/json"},
**kwargs)
response.raise_for_status()
payload = response.json()

if "error" in payload:
raise requests.HTTPError(payload['error'], response=response)

metadata = payload['meta']
metadata['latitude'] = metadata.pop('lat')
metadata['longitude'] = metadata.pop('lon')

df = pd.DataFrame(payload['data'], columns=['date', 'precipitation'])
rainfall = df.set_index('date')['precipitation']
rainfall = rainfall.replace(-999, np.nan)
rainfall.index = pd.to_datetime(rainfall.index)
return rainfall, metadata
37 changes: 37 additions & 0 deletions pvlib/tests/iotools/test_acis.py
@@ -0,0 +1,37 @@
"""
tests for :mod:`pvlib.iotools.acis`
"""

import pandas as pd
import pytest
from pvlib.iotools import get_acis_precipitation
from ..conftest import (RERUNS, RERUNS_DELAY, assert_series_equal)
from requests import HTTPError


@pytest.mark.parametrize('dataset,expected,lat,lon', [
(1, [0.76, 1.78, 1.52, 0.76, 0.0], 40.0, -80.0),
(2, [0.05, 2.74, 1.43, 0.92, 0.0], 40.0083, -79.9653),
(3, [0.0, 2.79, 1.52, 1.02, 0.0], 40.0, -80.0),
(21, [0.6, 1.8, 1.9, 1.2, 0.0], 40.0, -80.0),
])
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_acis_precipitation(dataset, expected, lat, lon):
st = '2012-01-01'
ed = '2012-01-05'
precipitation, meta = get_acis_precipitation(40, -80, st, ed, dataset)
idx = pd.date_range(st, ed, freq='d')
idx.name = 'date'
idx.freq = None
expected = pd.Series(expected, index=idx, name='precipitation')
assert_series_equal(precipitation, expected)
assert meta == {'latitude': lat, 'longitude': lon}


@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_acis_precipitation_error():
with pytest.raises(HTTPError, match='invalid grid'):
# 50 is not a valid dataset (or "grid", in ACIS lingo)
get_acis_precipitation(40, -80, '2012-01-01', '2012-01-05', 50)