diff --git a/docs/sphinx/source/reference/iotools.rst b/docs/sphinx/source/reference/iotools.rst index 514aeac2f5..14271cf3ee 100644 --- a/docs/sphinx/source/reference/iotools.rst +++ b/docs/sphinx/source/reference/iotools.rst @@ -31,6 +31,7 @@ of sources and file formats relevant to solar energy modeling. iotools.read_pvgis_tmy iotools.get_pvgis_hourly iotools.read_pvgis_hourly + iotools.get_pvgis_horizon iotools.get_bsrn iotools.read_bsrn iotools.parse_bsrn diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index 0950002edb..2401317532 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -15,6 +15,8 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* Added function to retrieve horizon data from PVGIS + :py:func:`pvlib.iotools.get_pvgis_horizon`. (:issue:`1290`, :pull:`1395`) * Added ``map_variables`` argument to the :py:func:`pvlib.iotools.read_tmy3` in order to offer the option of mapping column names to standard pvlib names. (:issue:`1517`, :pull:`1623`) @@ -25,7 +27,6 @@ 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`) - Bug fixes ~~~~~~~~~ * `data` can no longer be left unspecified in @@ -53,9 +54,17 @@ Contributors ~~~~~~~~~~~~ * Lakshya Garg (:ghuser:`Lakshyadevelops`) * Adam R. Jensen (:ghuser:`adamrjensen`) +* Ben Pierce (:ghuser:`bgpierc`) +* Joseph Palakapilly (:ghuser:`JPalakapillyKWH`) +* Cliff Hansen (:ghuser:`cwhanse`) +* Anton Driesse (:ghuser:`adriesse`) +* Will Holmgren (:ghuser:`wholmgren`) +* Mark Mikofski (:ghuser:`mikofski`) +* Karel De Brabandere (:ghuser:`kdebrab`) +* Josh Stein (:ghuser:`jsstein`) +* Kevin Anderson (:ghuser:`kandersolar`) * Siddharth Kaul (:ghuser:`k10blogger`) * Kshitiz Gupta (:ghuser:`kshitiz305`) * Stefan de Lange (:ghuser:`langestefan`) * :ghuser:`ooprathamm` * Kevin Anderson (:ghuser:`kandersolar`) - diff --git a/pvlib/data/test_read_pvgis_horizon.csv b/pvlib/data/test_read_pvgis_horizon.csv new file mode 100644 index 0000000000..0b3ed27bbc --- /dev/null +++ b/pvlib/data/test_read_pvgis_horizon.csv @@ -0,0 +1,49 @@ +horizon_azimuth,horizon_elevation +0,9.9 +7.5,13 +15,14.5 +22.5,15.7 +30,14.9 +37.5,15.3 +45,15.7 +52.5,15.7 +60,13 +67.5,11.5 +75,11.1 +82.5,11.5 +90,10.3 +97.5,11.5 +105,10.3 +112.5,9.5 +120,10.7 +127.5,11.8 +135,11.8 +142.5,8.8 +150,8.4 +157.5,7.3 +165,5.7 +172.5,5.7 +180,4.6 +187.5,3.4 +195,0.8 +202.5,0 +210,0 +217.5,0 +225,0 +232.5,0 +240,0 +247.5,0 +255,0 +262.5,0 +270,0 +277.5,0 +285,0 +292.5,0 +300,0 +307.5,0 +315,1.1 +322.5,1.9 +330,3.8 +337.5,5 +345,6.5 +352.5,9.2 diff --git a/pvlib/iotools/__init__.py b/pvlib/iotools/__init__.py index b02ce243ae..6f6a254a60 100644 --- a/pvlib/iotools/__init__.py +++ b/pvlib/iotools/__init__.py @@ -15,6 +15,7 @@ from pvlib.iotools.pvgis import get_pvgis_tmy, read_pvgis_tmy # noqa: F401 from pvlib.iotools.pvgis import read_pvgis_hourly # noqa: F401 from pvlib.iotools.pvgis import get_pvgis_hourly # noqa: F401 +from pvlib.iotools.pvgis import get_pvgis_horizon # noqa: F401 from pvlib.iotools.bsrn import get_bsrn # noqa: F401 from pvlib.iotools.bsrn import read_bsrn # noqa: F401 from pvlib.iotools.bsrn import parse_bsrn # noqa: F401 diff --git a/pvlib/iotools/pvgis.py b/pvlib/iotools/pvgis.py index edfb28c124..16bfee7cee 100644 --- a/pvlib/iotools/pvgis.py +++ b/pvlib/iotools/pvgis.py @@ -665,3 +665,57 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=None): data = data.rename(columns=VARIABLE_MAP) return data, months_selected, inputs, meta + + +def get_pvgis_horizon(latitude, longitude, url=URL, **kwargs): + """Get horizon data from PVGIS. + + Parameters + ---------- + latitude : float + Latitude in degrees north + longitude : float + Longitude in degrees east + url: str, default: :const:`pvlib.iotools.pvgis.URL` + Base URL for PVGIS + kwargs: + Passed to requests.get + + Returns + ------- + data : pd.Series + Pandas Series of the retrived horizon elevation angles. Index is the + corresponding horizon azimuth angles. + metadata : dict + Metadata returned by PVGIS. + + Notes + ----- + The horizon azimuths are specified clockwise from north, e.g., south=180. + This is the standard pvlib convention, although the PVGIS website specifies + south=0. + + References + ---------- + .. [1] `PVGIS horizon profile tool + `_ + """ + params = {'lat': latitude, 'lon': longitude, 'outputformat': 'json'} + res = requests.get(url + 'printhorizon', params=params, **kwargs) + if not res.ok: + try: + err_msg = res.json() + except Exception: + res.raise_for_status() + else: + raise requests.HTTPError(err_msg['message']) + json_output = res.json() + metadata = json_output['meta'] + data = pd.DataFrame(json_output['outputs']['horizon_profile']) + data.columns = ['horizon_azimuth', 'horizon_elevation'] + # Convert azimuth to pvlib convention (north=0, south=180) + data['horizon_azimuth'] += 180 + data.set_index('horizon_azimuth', inplace=True) + data = data['horizon_elevation'] # convert to pd.Series + data = data[data.index < 360] # remove duplicate north point (0 and 360) + return data, metadata diff --git a/pvlib/tests/iotools/test_pvgis.py b/pvlib/tests/iotools/test_pvgis.py index 579c26914c..a5a5e3fbd7 100644 --- a/pvlib/tests/iotools/test_pvgis.py +++ b/pvlib/tests/iotools/test_pvgis.py @@ -9,8 +9,9 @@ import requests from pvlib.iotools import get_pvgis_tmy, read_pvgis_tmy from pvlib.iotools import get_pvgis_hourly, read_pvgis_hourly +from pvlib.iotools import get_pvgis_horizon from ..conftest import (DATA_DIR, RERUNS, RERUNS_DELAY, assert_frame_equal, - fail_on_pvlib_version) + fail_on_pvlib_version, assert_series_equal) from pvlib._deprecation import pvlibDeprecationWarning @@ -509,6 +510,23 @@ def test_get_pvgis_map_variables(pvgis_tmy_mapped_columns): assert all([c in pvgis_tmy_mapped_columns for c in actual.columns]) +@pytest.mark.remote_data +@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) +def test_read_pvgis_horizon(): + pvgis_data, _ = get_pvgis_horizon(35.171051, -106.465158) + horizon_data = pd.read_csv(DATA_DIR / 'test_read_pvgis_horizon.csv', + index_col=0) + horizon_data = horizon_data['horizon_elevation'] + assert_series_equal(pvgis_data, horizon_data) + + +@pytest.mark.remote_data +@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY) +def test_read_pvgis_horizon_invalid_coords(): + with pytest.raises(requests.HTTPError, match='lat: Incorrect value'): + _, _ = get_pvgis_horizon(100, 50) # unfeasible latitude + + def test_read_pvgis_tmy_map_variables(pvgis_tmy_mapped_columns): fn = DATA_DIR / 'tmy_45.000_8.000_2005_2016.json' actual, _, _, _ = read_pvgis_tmy(fn, map_variables=True)