Skip to content

Commit 3766673

Browse files
committed
Resolves conflict in whatsnew file.
2 parents 3d8db5e + c3edec5 commit 3766673

10 files changed

Lines changed: 299 additions & 41 deletions

File tree

docs/sphinx/source/reference/iotools.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ clear-sky irradiance globally.
5757
iotools.parse_cams
5858

5959

60+
NASA POWER
61+
**********
62+
63+
Satellite-derived irradiance and weather data with global coverage.
64+
65+
.. autosummary::
66+
:toctree: generated/
67+
68+
iotools.get_nasa_power
69+
70+
6071
NSRDB
6172
*****
6273

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Breaking Changes
1010

1111
Deprecations
1212
~~~~~~~~~~~~
13+
* Deprecate :py:func:`~pvlib.modelchain.get_orientation`. (:pull:`2495`)
1314
* Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`.
1415
(:issue:`2479`, :pull:`2480`)
1516

@@ -23,7 +24,11 @@ Enhancements
2324
(:issue:`2479`, :pull:`2480`)
2425
* Add k coefficient in :py:func:`~pvlib.temperature.ross`
2526
(:issue:`2506`, :pull:`2521`)
26-
27+
* Add :py:func:`pvlib.iotools.get_nasa_power` to retrieve data from NASA POWER free API.
28+
(:pull:`2500`)
29+
* :py:func:`pvlib.spectrum.spectral_factor_firstsolar` no longer emits warnings
30+
when airmass and precipitable water values fall out of range. (:pull:`2512`)
31+
2732
Documentation
2833
~~~~~~~~~~~~~
2934
* Substantiate definitions of solar/surface azimuth/zenith and aoi on the
@@ -50,6 +55,8 @@ Maintenance
5055
Contributors
5156
~~~~~~~~~~~~
5257
* Elijah Passmore (:ghuser:`eljpsm`)
58+
* Ioannis Sifnaios (:ghuser:`IoannisSifnaios`)
5359
* Rajiv Daxini (:ghuser:`RDaxini`)
5460
* Omar Bahamida (:ghuser:`OmarBahamida`)
55-
* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`)
61+
* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`)
62+
* Kevin Anderson (:ghuser:`kandersolar`)

pvlib/iotools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@
3939
from pvlib.iotools.solcast import get_solcast_historic # noqa: F401
4040
from pvlib.iotools.solcast import get_solcast_tmy # noqa: F401
4141
from pvlib.iotools.solargis import get_solargis # noqa: F401
42+
from pvlib.iotools.nasa_power import get_nasa_power # noqa: F401

pvlib/iotools/nasa_power.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""Functions for reading and retrieving data from NASA POWER."""
2+
3+
import pandas as pd
4+
import requests
5+
import numpy as np
6+
7+
URL = 'https://power.larc.nasa.gov/api/temporal/hourly/point'
8+
9+
DEFAULT_PARAMETERS = [
10+
'dni', 'dhi', 'ghi', 'temp_air', 'wind_speed'
11+
]
12+
13+
VARIABLE_MAP = {
14+
'ALLSKY_SFC_SW_DWN': 'ghi',
15+
'ALLSKY_SFC_SW_DIFF': 'dhi',
16+
'ALLSKY_SFC_SW_DNI': 'dni',
17+
'CLRSKY_SFC_SW_DWN': 'ghi_clear',
18+
'T2M': 'temp_air',
19+
'WS2M': 'wind_speed_2m',
20+
'WS10M': 'wind_speed',
21+
}
22+
23+
24+
def get_nasa_power(latitude, longitude, start, end,
25+
parameters=DEFAULT_PARAMETERS, *, community='re',
26+
elevation=None, wind_height=None, wind_surface=None,
27+
map_variables=True, url=URL):
28+
"""
29+
Retrieve irradiance and weather data from NASA POWER.
30+
31+
A general description of NASA POWER is given in [1]_ and the API is
32+
described in [2]_. A detailed list of the available parameters can be
33+
found in [3]_.
34+
35+
Parameters
36+
----------
37+
latitude: float
38+
In decimal degrees, north is positive (ISO 19115).
39+
longitude: float
40+
In decimal degrees, east is positive (ISO 19115).
41+
start: datetime like
42+
First timestamp of the requested period.
43+
end: datetime like
44+
Last timestamp of the requested period.
45+
parameters: str, list
46+
List of parameters. The default parameters are mentioned below; for the
47+
full list see [3]_. Note that the pvlib naming conventions can also be
48+
used.
49+
50+
* Global Horizontal Irradiance (GHI) [Wm⁻²]
51+
* Diffuse Horizontal Irradiance (DHI) [Wm⁻²]
52+
* Direct Normal Irradiance (DNI) [Wm⁻²]
53+
* Air temperature at 2 m [C]
54+
* Wind speed at 10 m [m/s]
55+
56+
community: str, default 're'
57+
Can be one of the following depending on which parameters are of
58+
interest. Note that in many cases this choice
59+
might affect the units of the parameter.
60+
61+
* ``'re'``: renewable energy
62+
* ``'sb'``: sustainable buildings
63+
* ``'ag'``: agroclimatology
64+
65+
elevation: float, optional
66+
The custom site elevation in meters to produce the corrected
67+
atmospheric pressure adjusted for elevation.
68+
wind_height: float, optional
69+
The custom wind height in meters to produce the wind speed adjusted
70+
for height. Has to be between 10 and 300 m; see [4]_.
71+
wind_surface: str, optional
72+
The definable surface type to adjust the wind speed. For a list of the
73+
surface types see [4]_. If you provide a wind surface alias please
74+
include a site elevation with the request.
75+
map_variables: bool, default True
76+
When true, renames columns of the Dataframe to pvlib variable names
77+
where applicable. See variable :const:`VARIABLE_MAP`.
78+
79+
Raises
80+
------
81+
requests.HTTPError
82+
Raises an error when an incorrect request is made.
83+
84+
Returns
85+
-------
86+
data : pd.DataFrame
87+
Time series data. The index corresponds to the start (left) of the
88+
interval.
89+
meta : dict
90+
Metadata.
91+
92+
References
93+
----------
94+
.. [1] `NASA Prediction Of Worldwide Energy Resources (POWER)
95+
<https://power.larc.nasa.gov/>`_
96+
.. [2] `NASA POWER API
97+
<https://power.larc.nasa.gov/api/pages/>`_
98+
.. [3] `NASA POWER API parameters
99+
<https://power.larc.nasa.gov/parameters/>`_
100+
.. [4] `NASA POWER corrected wind speed parameters
101+
<https://power.larc.nasa.gov/docs/methodology/meteorology/wind/>`_
102+
"""
103+
start = pd.Timestamp(start)
104+
end = pd.Timestamp(end)
105+
106+
# allow the use of pvlib parameter names
107+
parameter_dict = {v: k for k, v in VARIABLE_MAP.items()}
108+
parameters = [parameter_dict.get(p, p) for p in parameters]
109+
110+
params = {
111+
'latitude': latitude,
112+
'longitude': longitude,
113+
'start': start.strftime('%Y%m%d'),
114+
'end': end.strftime('%Y%m%d'),
115+
'community': community,
116+
'parameters': ','.join(parameters), # make parameters in a string
117+
'format': 'json',
118+
'user': None,
119+
'header': True,
120+
'time-standard': 'utc',
121+
'site-elevation': elevation,
122+
'wind-elevation': wind_height,
123+
'wind-surface': wind_surface,
124+
}
125+
126+
response = requests.get(url, params=params)
127+
if not response.ok:
128+
# response.raise_for_status() does not give a useful error message
129+
raise requests.HTTPError(response.json())
130+
131+
# Parse the data to dataframe
132+
data = response.json()
133+
hourly_data = data['properties']['parameter']
134+
df = pd.DataFrame(hourly_data)
135+
df.index = pd.to_datetime(df.index, format='%Y%m%d%H').tz_localize('UTC')
136+
137+
# Create metadata dictionary
138+
meta = data['header']
139+
meta['times'] = data['times']
140+
meta['parameters'] = data['parameters']
141+
142+
meta['longitude'] = data['geometry']['coordinates'][0]
143+
meta['latitude'] = data['geometry']['coordinates'][1]
144+
meta['altitude'] = data['geometry']['coordinates'][2]
145+
146+
# Replace NaN values
147+
df = df.replace(meta['fill_value'], np.nan)
148+
149+
# Rename according to pvlib convention
150+
if map_variables:
151+
df = df.rename(columns=VARIABLE_MAP)
152+
153+
return df, meta

pvlib/iotools/pvgis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
202202
.. [2] `PVGIS Hourly Radiation
203203
<https://ec.europa.eu/jrc/en/PVGIS/tools/hourly-radiation>`_
204204
.. [3] `PVGIS Non-interactive service
205-
<https://ec.europa.eu/jrc/en/PVGIS/docs/noninteractive>`_
205+
<https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/getting-started-pvgis/api-non-interactive-service_en>`_
206206
.. [4] `PVGIS horizon profile tool
207207
<https://ec.europa.eu/jrc/en/PVGIS/tools/horizon>`_
208208
""" # noqa: E501

pvlib/modelchain.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from pvlib.pvsystem import _DC_MODEL_PARAMS
1919
from pvlib.tools import _build_kwargs
2020

21+
from pvlib._deprecation import deprecated
22+
2123
# keys that are used to detect input data and assign data to appropriate
2224
# ModelChain attribute
2325
# for ModelChain.weather
@@ -59,6 +61,13 @@
5961
)
6062

6163

64+
@deprecated(
65+
since="0.13.1",
66+
removal="",
67+
name="pvlib.modelchain.get_orientation",
68+
alternative=None,
69+
addendum=None,
70+
)
6271
def get_orientation(strategy, **kwargs):
6372
"""
6473
Determine a PV system's surface tilt and surface azimuth

pvlib/spectrum/mismatch.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -239,27 +239,15 @@ def spectral_factor_firstsolar(precipitable_water, airmass_absolute,
239239
"""
240240
pw = np.atleast_1d(precipitable_water)
241241
pw = pw.astype('float64')
242-
if np.min(pw) < min_precipitable_water:
243-
pw = np.maximum(pw, min_precipitable_water)
244-
warn('Low precipitable water values replaced with '
245-
f'{min_precipitable_water} cm in the calculation of spectral '
246-
'mismatch.')
247-
248-
if np.max(pw) > max_precipitable_water:
249-
pw[pw > max_precipitable_water] = np.nan
250-
warn('High precipitable water values replaced with np.nan in '
251-
'the calculation of spectral mismatch.')
242+
pw = np.maximum(pw, min_precipitable_water)
243+
pw[pw > max_precipitable_water] = np.nan
252244

253245
airmass_absolute = np.minimum(airmass_absolute, max_airmass_absolute)
254-
255-
if np.min(airmass_absolute) < min_airmass_absolute:
256-
airmass_absolute = np.maximum(airmass_absolute, min_airmass_absolute)
257-
warn('Low airmass values replaced with 'f'{min_airmass_absolute} in '
258-
'the calculation of spectral mismatch.')
259-
# pvlib.atmosphere.get_absolute_airmass(1,
260-
# pvlib.atmosphere.alt2pres(4340)) = 0.58 Elevation of
261-
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
262-
# population over 50,000.
246+
# pvlib.atmosphere.get_absolute_airmass(1,
247+
# pvlib.atmosphere.alt2pres(4340)) = 0.58 Elevation of
248+
# Mina Pirquita, Argentian = 4340 m. Highest elevation city with
249+
# population over 50,000.
250+
airmass_absolute = np.maximum(airmass_absolute, min_airmass_absolute)
263251

264252
_coefficients = {}
265253
_coefficients['cdte'] = (

tests/iotools/test_nasa_power.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import pandas as pd
2+
import pytest
3+
import pvlib
4+
from requests.exceptions import HTTPError
5+
from tests.conftest import RERUNS, RERUNS_DELAY
6+
7+
8+
@pytest.fixture
9+
def data_index():
10+
index = pd.date_range(start='2025-02-02 00:00+00:00',
11+
end='2025-02-02 23:00+00:00', freq='1h')
12+
return index
13+
14+
15+
@pytest.fixture
16+
def ghi_series(data_index):
17+
ghi = [
18+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 50.25, 184.2, 281.55, 368.3, 406.48,
19+
386.45, 316.05, 210.1, 109.05, 12.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
20+
]
21+
return pd.Series(data=ghi, index=data_index, name='ghi')
22+
23+
24+
@pytest.mark.remote_data
25+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
26+
def test_get_nasa_power(data_index, ghi_series):
27+
data, meta = pvlib.iotools.get_nasa_power(latitude=44.76,
28+
longitude=7.64,
29+
start=data_index[0],
30+
end=data_index[-1],
31+
parameters=['ALLSKY_SFC_SW_DWN'],
32+
map_variables=False)
33+
# Check that metadata is correct
34+
assert meta['latitude'] == 44.76
35+
assert meta['longitude'] == 7.64
36+
assert meta['altitude'] == 705.88
37+
assert meta['start'] == '20250202'
38+
assert meta['end'] == '20250202'
39+
assert meta['time_standard'] == 'UTC'
40+
assert meta['title'] == 'NASA/POWER Source Native Resolution Hourly Data'
41+
# Assert that the index is parsed correctly
42+
pd.testing.assert_index_equal(data.index, data_index)
43+
# Test one column
44+
pd.testing.assert_series_equal(data['ALLSKY_SFC_SW_DWN'], ghi_series,
45+
check_freq=False, check_names=False)
46+
47+
48+
@pytest.mark.remote_data
49+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
50+
def test_get_nasa_power_pvlib_params_naming(data_index, ghi_series):
51+
data, meta = pvlib.iotools.get_nasa_power(latitude=44.76,
52+
longitude=7.64,
53+
start=data_index[0],
54+
end=data_index[-1],
55+
parameters=['ghi'])
56+
# Assert that the index is parsed correctly
57+
pd.testing.assert_index_equal(data.index, data_index)
58+
# Test one column
59+
pd.testing.assert_series_equal(data['ghi'], ghi_series,
60+
check_freq=False)
61+
62+
63+
@pytest.mark.remote_data
64+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
65+
def test_get_nasa_power_map_variables(data_index):
66+
# Check that variables are mapped by default to pvlib names
67+
data, meta = pvlib.iotools.get_nasa_power(latitude=44.76,
68+
longitude=7.64,
69+
start=data_index[0],
70+
end=data_index[-1])
71+
mapped_column_names = ['ghi', 'dni', 'dhi', 'temp_air', 'wind_speed']
72+
for c in mapped_column_names:
73+
assert c in data.columns
74+
assert meta['latitude'] == 44.76
75+
assert meta['longitude'] == 7.64
76+
assert meta['altitude'] == 705.88
77+
78+
79+
@pytest.mark.remote_data
80+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
81+
def test_get_nasa_power_wrong_parameter_name(data_index):
82+
# Test if HTTPError is raised if a wrong parameter name is asked
83+
with pytest.raises(HTTPError, match=r"ALLSKY_SFC_SW_DLN"):
84+
pvlib.iotools.get_nasa_power(latitude=44.76,
85+
longitude=7.64,
86+
start=data_index[0],
87+
end=data_index[-1],
88+
parameters=['ALLSKY_SFC_SW_DLN'])
89+
90+
91+
@pytest.mark.remote_data
92+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
93+
def test_get_nasa_power_duplicate_parameter_name(data_index):
94+
# Test if HTTPError is raised if a duplicate parameter is asked
95+
with pytest.raises(HTTPError, match=r"ALLSKY_SFC_SW_DWN"):
96+
pvlib.iotools.get_nasa_power(latitude=44.76,
97+
longitude=7.64,
98+
start=data_index[0],
99+
end=data_index[-1],
100+
parameters=2*['ALLSKY_SFC_SW_DWN'])

0 commit comments

Comments
 (0)