Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion docs/sphinx/source/reference/iotools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ Meteonorm
.. autosummary::
:toctree: generated/

iotools.get_meteonorm
iotools.get_meteonorm_observation
iotools.get_meteonorm_forecast
iotools.get_meteonorm_tmy


Expand Down
3 changes: 2 additions & 1 deletion docs/sphinx/source/whatsnew/v0.13.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ Bug fixes
Enhancements
~~~~~~~~~~~~
* Add iotools functions to retrieve irradiance and weather data from Meteonorm:
:py:func:`~pvlib.iotools.get_meteonorm` and :py:func:`~pvlib.iotools.get_meteonorm_tmy`.
:py:func:`~pvlib.iotools.get_meteonorm_observation`, :py:func:`~pvlib.iotools.get_meteonorm_forecast`,
and :py:func:`~pvlib.iotools.get_meteonorm_tmy`.
(:pull:`2499`)
* Add :py:func:`pvlib.iotools.get_nasa_power` to retrieve data from NASA POWER free API.
(:pull:`2500`)
Expand Down
3 changes: 2 additions & 1 deletion pvlib/iotools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from pvlib.iotools.solcast import get_solcast_historic # noqa: F401
from pvlib.iotools.solcast import get_solcast_tmy # noqa: F401
from pvlib.iotools.solargis import get_solargis # noqa: F401
from pvlib.iotools.meteonorm import get_meteonorm # noqa: F401
from pvlib.iotools.meteonorm import get_meteonorm_observation # noqa: F401
from pvlib.iotools.meteonorm import get_meteonorm_forecast # noqa: F401
from pvlib.iotools.meteonorm import get_meteonorm_tmy # noqa: F401
from pvlib.iotools.nasa_power import get_nasa_power # noqa: F401
154 changes: 133 additions & 21 deletions pvlib/iotools/meteonorm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,19 @@
}


def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
parameters='all', *, surface_tilt=0, surface_azimuth=180,
time_step='15min', horizon='auto', interval_index=False,
map_variables=True, url=URL):
def get_meteonorm_observation(
latitude, longitude, start, end, api_key, endpoint='training',
Comment thread
AdamRJensen marked this conversation as resolved.
Outdated
parameters='all', *, surface_tilt=0, surface_azimuth=180,
time_step='15min', horizon='auto', interval_index=False,
map_variables=True, url=URL):
"""
Retrieve irradiance and weather data from Meteonorm.
Retrieve historical and near real-time observational data from Meteonorm.

The Meteonorm data options are described in [1]_ and the API is described
in [2]_. A detailed list of API options can be found in [3]_.

This function supports retrieval of historical and forecast data, but not
TMY.
This function supports retrieval of observation data, either the
'training' or the 'realtime' endpoints.

Parameters
----------
Expand All @@ -58,14 +59,11 @@
specified, UTC is assumed.
api_key : str
Meteonorm API key.
endpoint : str
endpoint : str, default : training
API endpoint, see [3]_. Must be one of:

* ``'observation/training'`` - historical data with a 7-day delay
* ``'observation/realtime'`` - near-real time (past 7-days)
* ``'forecast/basic'`` - forecast with hourly resolution
* ``'forecast/precision'`` - forecast with 1-min, 15-min, or hourly
resolution
* ``'training'`` - historical data with a 7-day delay
* ``'realtime'`` - near-real time (past 7-days)

parameters : list or 'all', default : 'all'
List of parameters to request or `'all'` to get all parameters.
Expand All @@ -75,8 +73,7 @@
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north
(north=0, east=90, south=180, west=270).
time_step : {'1min', '15min', '1h'}, default : '15min'
Frequency of the time series. The endpoint ``'forecast/basic'`` only
supports ``time_step='1h'``.
Frequency of the time series.
horizon : str or list, default : 'auto'
Specification of the horizon line. Can be either 'flat', 'auto', or
a list of 360 integer horizon elevation angles.
Expand All @@ -87,8 +84,7 @@
When true, renames columns of the Dataframe to pvlib variable names
where applicable. See variable :const:`VARIABLE_MAP`.
url : str, optional
Base URL of the Meteonorm API. The ``endpoint`` parameter is
appended to the url. The default is
Base URL of the Meteonorm API. The default is
:const:`pvlib.iotools.meteonorm.URL`.

Raises
Expand All @@ -107,11 +103,11 @@
Examples
--------
>>> # Retrieve historical time series data
>>> df, meta = pvlib.iotools.get_meteonorm( # doctest: +SKIP
>>> df, meta = pvlib.iotools.get_meteonorm_observatrion( # doctest: +SKIP
... latitude=50, longitude=10, # doctest: +SKIP
... start='2023-01-01', end='2025-01-01', # doctest: +SKIP
... api_key='redacted', # doctest: +SKIP
... endpoint='observation/training') # doctest: +SKIP
... endpoint='training') # doctest: +SKIP

See Also
--------
Expand All @@ -126,6 +122,120 @@
.. [3] `Meteonorm API reference
<https://docs.meteonorm.com/api>`_
"""
endpoint_base = 'observation/'

data, meta = _get_meteonorm(
latitude, longitude, start, end, api_key,

Check failure on line 128 in pvlib/iotools/meteonorm.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E126 continuation line over-indented for hanging indent
endpoint_base, endpoint,
parameters, surface_tilt, surface_azimuth,
time_step, horizon, interval_index,
map_variables, url)
return data, meta


def get_meteonorm_forecast(
latitude, longitude, start, end, api_key, endpoint='precision',
parameters='all', *, surface_tilt=0, surface_azimuth=180,
time_step='15min', horizon='auto', interval_index=False,
map_variables=True, url=URL):
"""
Retrieve historical and near real-time observational data from Meteonorm.

The Meteonorm data options are described in [1]_ and the API is described
in [2]_. A detailed list of API options can be found in [3]_.

This function supports retrieval of forecasting data, either the
'training' or the 'basic' endpoints.

Parameters
----------
latitude : float
In decimal degrees, north is positive (ISO 19115).
longitude: float
In decimal degrees, east is positive (ISO 19115).
start : datetime like
First timestamp of the requested period. If a timezone is not
specified, UTC is assumed.
end : datetime like
Last timestamp of the requested period. If a timezone is not
specified, UTC is assumed.
api_key : str
Meteonorm API key.
endpoint : str, default : precision
API endpoint, see [3]_. Must be one of:

* ``'precision'`` - forecast with 1-min, 15-min, or hourly
resolution
* ``'basic'`` - forecast with hourly resolution

parameters : list or 'all', default : 'all'
List of parameters to request or `'all'` to get all parameters.
surface_tilt : float, default : 0
Tilt angle from horizontal plane.
surface_azimuth : float, default : 180
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north
(north=0, east=90, south=180, west=270).
time_step : {'1min', '15min', '1h'}, default : '15min'
Frequency of the time series. The endpoint ``'basic'`` only
supports ``time_step='1h'``.
horizon : str or list, default : 'auto'
Specification of the horizon line. Can be either 'flat', 'auto', or
a list of 360 integer horizon elevation angles.
interval_index : bool, default : False
Index is pd.DatetimeIndex when False, and pd.IntervalIndex when True.
This is an experimental feature which may be removed without warning.
map_variables : bool, default : True
When true, renames columns of the Dataframe to pvlib variable names
where applicable. See variable :const:`VARIABLE_MAP`.
url : str, optional
Base URL of the Meteonorm API. The default is
:const:`pvlib.iotools.meteonorm.URL`.

Raises
------
requests.HTTPError
Raises an error when an incorrect request is made.

Returns
-------
data : pd.DataFrame
Time series data. The index corresponds to the start (left) of the
interval unless ``interval_index`` is set to True.
meta : dict
Metadata.

See Also
--------
pvlib.iotools.get_meteonorm_observation,
pvlib.iotools.get_meteonorm_tmy

References
----------
.. [1] `Meteonorm
<https://meteonorm.com/>`_
.. [2] `Meteonorm API
<https://docs.meteonorm.com/docs/getting-started>`_
.. [3] `Meteonorm API reference
<https://docs.meteonorm.com/api>`_
"""
endpoint_base = 'forecast/'

data, meta = _get_meteonorm(
latitude, longitude, start, end, api_key,

Check failure on line 224 in pvlib/iotools/meteonorm.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E126 continuation line over-indented for hanging indent
endpoint_base, endpoint,
parameters, surface_tilt, surface_azimuth,
time_step, horizon, interval_index,
map_variables, url)
return data, meta


def _get_meteonorm(
latitude, longitude, start, end, api_key,
endpoint_base, endpoint,
parameters, surface_tilt, surface_azimuth,
time_step, horizon, interval_index,
map_variables, url):

# Relative date strings are not yet supported
Comment thread
kandersolar marked this conversation as resolved.
Outdated
start = pd.Timestamp(start)
end = pd.Timestamp(end)
Expand Down Expand Up @@ -167,7 +277,8 @@
headers = {"Authorization": f"Bearer {api_key}"}

response = requests.get(
urljoin(url, endpoint.lstrip('/')), headers=headers, params=params)
urljoin(url, endpoint_base + endpoint.lstrip('/')),
headers=headers, params=params)

if not response.ok:
# response.raise_for_status() does not give a useful error message
Expand Down Expand Up @@ -265,7 +376,8 @@

See Also
--------
pvlib.iotools.get_meteonorm
pvlib.iotools.get_meteonorm_observation,
pvlib.iotools.get_meteonorm_forecast
Comment thread
AdamRJensen marked this conversation as resolved.
Outdated

References
----------
Expand Down
30 changes: 15 additions & 15 deletions tests/iotools/test_meteonorm.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ def expected_columns_all():
def test_get_meteonorm_training(
demo_api_key, demo_url, expected_meta, expected_meteonorm_index,
expected_metenorm_data):
data, meta = pvlib.iotools.get_meteonorm(
data, meta = pvlib.iotools.get_meteonorm_observation(
latitude=50, longitude=10,
start='2023-01-01', end='2025-01-01',
api_key=demo_api_key,
parameters=['ghi', 'global_horizontal_irradiance_with_shading'],
endpoint='observation/training',
endpoint='training',
time_step='1h',
url=demo_url)

Expand All @@ -128,14 +128,14 @@ def test_get_meteonorm_training(
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_meteonorm_realtime(demo_api_key, demo_url, expected_columns_all):
data, meta = pvlib.iotools.get_meteonorm(
data, meta = pvlib.iotools.get_meteonorm_observation(
latitude=21, longitude=79,
start=pd.Timestamp.now(tz='UTC') - pd.Timedelta(hours=5),
end=pd.Timestamp.now(tz='UTC') - pd.Timedelta(hours=1),
surface_tilt=20, surface_azimuth=10,
parameters=['all'],
api_key=demo_api_key,
endpoint='/observation/realtime',
endpoint='realtime',
time_step='1min',
horizon='flat',
map_variables=False,
Expand All @@ -158,14 +158,14 @@ def test_get_meteonorm_realtime(demo_api_key, demo_url, expected_columns_all):
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_meteonorm_forecast_basic(demo_api_key, demo_url):
data, meta = pvlib.iotools.get_meteonorm(
data, meta = pvlib.iotools.get_meteonorm_forecast(
latitude=50, longitude=10,
start=pd.Timestamp.now(tz='UTC'),
end=pd.Timestamp.now(tz='UTC') + pd.Timedelta(hours=5),
time_step='1h',
api_key=demo_api_key,
parameters='ghi',
endpoint='forecast/basic',
endpoint='basic',
url=demo_url)

assert data.shape == (6, 1)
Expand All @@ -177,13 +177,13 @@ def test_get_meteonorm_forecast_basic(demo_api_key, demo_url):
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_meteonorm_forecast_precision(demo_api_key, demo_url):
data, meta = pvlib.iotools.get_meteonorm(
data, meta = pvlib.iotools.get_meteonorm_forecast(
latitude=50, longitude=10,
start=pd.Timestamp.now(tz='UTC') + pd.Timedelta(hours=5),
end=pd.Timestamp.now(tz='UTC') + pd.Timedelta(hours=6),
api_key=demo_api_key,
parameters='ghi',
endpoint='forecast/precision',
endpoint='precision',
time_step='15min',
url=demo_url)

Expand All @@ -195,45 +195,45 @@ def test_get_meteonorm_forecast_precision(demo_api_key, demo_url):
@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_meteonorm_custom_horizon(demo_api_key, demo_url):
data, meta = pvlib.iotools.get_meteonorm(
data, meta = pvlib.iotools.get_meteonorm_forecast(
latitude=50, longitude=10,
start=pd.Timestamp.now(tz='UTC'),
end=pd.Timestamp.now(tz='UTC') + pd.Timedelta(hours=5),
api_key=demo_api_key,
parameters='ghi',
time_step='1h',
endpoint='forecast/basic',
endpoint='basic',
horizon=list(np.ones(360).astype(int)*80),
url=demo_url)


@pytest.mark.remote_data
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
def test_get_meteonorm_HTTPError(demo_api_key, demo_url):
def test_get_meteonorm_forecast_HTTPError(demo_api_key, demo_url):
with pytest.raises(
HTTPError, match="unknown parameter: not_a_real_parameter"):
_ = pvlib.iotools.get_meteonorm(
_ = pvlib.iotools.get_meteonorm_forecast(
latitude=50, longitude=10,
start=pd.Timestamp.now(tz='UTC'),
end=pd.Timestamp.now(tz='UTC') + pd.Timedelta(hours=5),
time_step='1h',
api_key=demo_api_key,
parameters='not_a_real_parameter',
endpoint='forecast/basic',
endpoint='basic',
url=demo_url)


def test_get_meteonorm_basic_forecast_incorrect_time_step(
demo_api_key, demo_url):
with pytest.raises(
ValueError, match="only supports ``time_step='1h'``"):
_ = pvlib.iotools.get_meteonorm(
_ = pvlib.iotools.get_meteonorm_forecast(
latitude=50, longitude=10,
start=pd.Timestamp.now(tz='UTC'),
end=pd.Timestamp.now(tz='UTC') + pd.Timedelta(hours=5),
time_step='15min', # only '1h' is supported for tmy
api_key=demo_api_key,
endpoint='forecast/basic',
endpoint='basic',
url=demo_url)


Expand Down
Loading