Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/sphinx/source/whatsnew/v0.15.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Enhancements
* Added mapping of the parameter ``"albedo"`` in
:py:func:`~pvlib.iotools.get_nasa_power` when ``map_variables=True``
(:pull:`2753`)
* Add the following parameters to :py:func:`~pvlib.iotools.get_nasa_power`
when ``map_variables=True``: ``temp_dew``, ``precipitable_water``,
``relative_humidity``, ``ghi_extra``, ``dhi_clear``, and ``longwave_down``
(:pull:`2762`)
Comment thread
AdamRJensen marked this conversation as resolved.
Outdated


Documentation
Expand Down Expand Up @@ -62,3 +66,4 @@ Contributors
* Cliff Hansen (:ghuser:`cwhanse`)
* Arthur Onno (:ghuser:`ArthurOnnoTerabase`)
* Adam R. Jensen (:ghuser:`AdamRJensen`)
* Karl Hill (:ghuser:`karlhillx`)
22 changes: 21 additions & 1 deletion pvlib/iotools/nasa_power.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@
'ALLSKY_SFC_SW_DWN': 'ghi',
'ALLSKY_SFC_SW_DIFF': 'dhi',
'ALLSKY_SFC_SW_DNI': 'dni',
'ALLSKY_SRF_ALB': 'albedo',
'ALLSKY_SFC_LW_DWN': 'longwave_down',
'CLRSKY_SFC_SW_DIFF': 'dhi_clear',
Comment thread
AdamRJensen marked this conversation as resolved.
'CLRSKY_SFC_SW_DNI': 'dni_clear',
'CLRSKY_SFC_SW_DWN': 'ghi_clear',
'PS': 'pressure',
'RH2M': 'relative_humidity',
'T2M': 'temp_air',
'T2MDEW': 'temp_dew',
'TQV': 'precipitable_water',
Comment thread
karlhillx marked this conversation as resolved.
'TOA_SW_DWN': 'ghi_extra',
'WS2M': 'wind_speed_2m',
'WS10M': 'wind_speed',
'ALLSKY_SRF_ALB': 'albedo',
}


Expand Down Expand Up @@ -82,6 +90,12 @@ def get_nasa_power(latitude, longitude, start, end,
requests.HTTPError
Raises an error when an incorrect request is made.

Notes
-----
When ``map_variables=True`` the following unit conversions are applied:
pressure is converted from kPa to Pa, and precipitable water
is converted from kg/m² (mm) to cm.

Returns
-------
data : pd.DataFrame
Expand Down Expand Up @@ -150,5 +164,11 @@ def get_nasa_power(latitude, longitude, start, end,
# Rename according to pvlib convention
if map_variables:
df = df.rename(columns=VARIABLE_MAP)
# PS is returned in kPa; convert to Pa for pvlib compatibility.
if 'pressure' in df.columns:
df['pressure'] = df['pressure'] * 1000
# TQV is returned in kg/m^2 (=mm); convert to cm for compatibility
if 'precipitable_water' in df.columns:
df['precipitable_water'] = df['precipitable_water'] / 10

return df, meta
107 changes: 107 additions & 0 deletions tests/iotools/test_nasa_power.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,110 @@ def test_get_nasa_power_duplicate_parameter_name(data_index):
start=data_index[0],
end=data_index[-1],
parameters=2*['ALLSKY_SFC_SW_DWN'])


@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_nasa_power_all_variable_map_parameters_valid():
"""
Every NASA POWER parameter name in VARIABLE_MAP must be accepted by the
live API. A typo or stale name (e.g. CLRSKY_DIFF vs CLRSKY_SFC_SW_DIFF)
causes the API to return an HTTPError, which would fail this test.

NASA POWER allows max 15 parameters per request; VARIABLE_MAP is split
into two batches to stay within that limit.
"""
from pvlib.iotools.nasa_power import VARIABLE_MAP
nasa_params = list(VARIABLE_MAP.keys())
# Split into two batches; API limit is 15.
half = (len(nasa_params) + 1) // 2
batch1 = nasa_params[:half]
batch2 = nasa_params[half:]

for batch in [batch1, batch2]:
data, meta = pvlib.iotools.get_nasa_power(
latitude=44.76,
longitude=7.64,
start='2025-02-02',
end='2025-02-02',
parameters=batch,
map_variables=False,
)
missing = set(batch) - set(data.columns)
assert not missing, f"NASA POWER did not return: {sorted(missing)}"


@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_nasa_power_all_variable_map_renamed():
"""
With map_variables=True every NASA name must be renamed to its pvlib
equivalent. Catches duplicate-target collisions (e.g. two entries both
mapping to 'temp_dew', which silently drops a column under pandas rename).
"""
from pvlib.iotools.nasa_power import VARIABLE_MAP
nasa_params = list(VARIABLE_MAP.keys())
pvlib_names = set(VARIABLE_MAP.values())

data, _ = pvlib.iotools.get_nasa_power(
latitude=44.76,
longitude=7.64,
start='2025-02-02',
end='2025-02-02',
parameters=nasa_params,
map_variables=True,
)

missing = pvlib_names - set(data.columns)
assert not missing, (
f"map_variables=True dropped pvlib columns: {sorted(missing)}. "
"Likely cause: duplicate target names in VARIABLE_MAP."
)


@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_nasa_power_pressure_unit_conversion():
"""
NASA POWER returns PS in kPa; pvlib convention is Pa.
Sea-level surface pressure should be ~101 kPa = ~101325 Pa, not ~101.
"""
data, _ = pvlib.iotools.get_nasa_power(
latitude=0.0, # sea-level equatorial point
longitude=-30.0,
start='2025-02-02',
end='2025-02-02',
parameters=['PS'],
map_variables=True,
)
mean_pressure = data['pressure'].mean()
# Anywhere on earth, surface pressure in Pa is between ~50k and ~110k.
# If the conversion is missing, value will be ~100 (kPa) and fail this.
assert 50_000 < mean_pressure < 110_000, (
f"PS not converted from kPa to Pa. Got mean={mean_pressure}"
)


@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_nasa_power_precipitable_water_unit_conversion():
"""
NASA POWER returns TQV in kg/m^2 (= mm of water column);
pvlib convention is cm. Typical atmospheric column is 1-5 cm,
never above ~7 cm. If the /10 conversion is missing, values will
be 10-50 and fail this bound.
"""
data, _ = pvlib.iotools.get_nasa_power(
latitude=0.0, # tropics: high water vapor, worst case for bounds
longitude=-60.0,
start='2025-02-02',
end='2025-02-02',
parameters=['TQV'],
map_variables=True,
)
mean_pw = data['precipitable_water'].mean()
# pvlib precipitable_water is in cm. Tropical column is <~7 cm.
# Missing /10 conversion would give 10-70 (kg/m^2 = mm).
assert 0 < mean_pw < 10, (
f"TQV not converted from kg/m^2 to cm. Got mean={mean_pw}"
)