From 7ca85bb063f760eaee443a2d601802f144e6b2de Mon Sep 17 00:00:00 2001
From: "Adam R. Jensen" <39184289+AdamRJensen@users.noreply.github.com>
Date: Tue, 15 Jul 2025 09:44:36 +0200
Subject: [PATCH 1/3] Create meteonorm.py
---
pvlib/iotools/meteonorm.py | 179 +++++++++++++++++++++++++++++++++++++
1 file changed, 179 insertions(+)
create mode 100644 pvlib/iotools/meteonorm.py
diff --git a/pvlib/iotools/meteonorm.py b/pvlib/iotools/meteonorm.py
new file mode 100644
index 0000000000..d9897b8ceb
--- /dev/null
+++ b/pvlib/iotools/meteonorm.py
@@ -0,0 +1,179 @@
+"""Functions for reading and retrieving data from Meteonorm."""
+
+import pandas as pd
+import requests
+from urllib.parse import urljoin
+
+URL = 'https://api.meteonorm.com/v1/'
+
+VARIABLE_MAP = {
+ 'global_horizontal_irradiance': 'ghi',
+ 'diffuse_horizontal_irradiance': 'dhi',
+ 'direct_normal_irradiance': 'dni',
+ 'direct_horizontal_irradiance': 'bhi',
+ 'global_clear_sky_irradiance': 'ghi_clear',
+ 'diffuse_tilted_irradiance': 'poa_diffuse',
+ 'direct_tilted_irradiance': 'poa_direct',
+ 'global_tilted_irradiance': 'poa',
+ 'temperature': 'temp_air',
+ 'dew_point_temperature': 'temp_dew',
+}
+
+time_step_map = {
+ '1h': '1_hour',
+ 'h': '1_hour',
+ '15min': '15_minutes',
+ '1min': '1_minute',
+ 'min': '1_minute',
+}
+
+
+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):
+ """
+ Retrieve irradiance and weather 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 the end points 'realtime' for data for the past 7
+ days, 'training' for historical data with a delay of 7 days. The function
+ does not support TMY climate data.
+
+ Parameters
+ ----------
+ latitude: float
+ In decimal degrees, north is positive (ISO 19115).
+ longitude: float
+ In decimal degrees, east is positive (ISO 19115).
+ start: datetime like, optional
+ First timestamp of the requested period. If a timezone is not
+ specified, UTC is assumed. A relative datetime string is also allowed.
+ end: datetime like, optional
+ Last timestamp of the requested period. If a timezone is not
+ specified, UTC is assumed. A relative datetime string is also allowed.
+ api_key: str
+ Meteonorm API key.
+ endpoint : str
+ API end point, see [3]_. Must be one of:
+
+ * '/observation/training'
+ * '/observation/realtime'
+ * '/forecast/basic'
+ * '/forecast/precision'
+
+ parameters : list, optional
+ List of parameters to request or "all" to get all parameters. The
+ default is "all".
+ 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'}, optional
+ ime step of the time series. The default is '15min'. Ignored if
+ requesting forecast data.
+ horizon : optional
+ Specification of the hoirzon line. Can be either 'flat' or 'auto', or
+ specified as a list of 360 horizon elevation angles. The default is
+ 'auto'.
+ interval_index: bool, optional
+ Whether the index of the returned data object is of the type
+ pd.DatetimeIndex or pd.IntervalIndex. This is an experimental feature
+ which may be removed without warning. The default is False.
+ map_variables: bool, default: True
+ When true, renames columns of the Dataframe to pvlib variable names
+ where applicable. The default is True. See variable
+ :const:`VARIABLE_MAP`.
+ url: str, default: :const:`pvlib.iotools.meteonorm.URL`
+ Base url of the Meteonorm API. The ``endpoint`` parameter is
+ appended to the 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.
+ meta : dict
+ Metadata.
+
+ See Also
+ --------
+ pvlib.iotools.get_meteonorm_tmy
+
+ References
+ ----------
+ .. [1] `Meteonorm
+ `_
+ .. [2] `Meteonorm API
+ `_
+ .. [3] `Meteonorm API reference
+ `_
+ """
+ start = pd.Timestamp(start)
+ end = pd.Timestamp(end)
+ start = start.tz_localize('UTC') if start.tzinfo is None else start
+ end = end.tz_localize('UTC') if end.tzinfo is None else end
+
+ params = {
+ 'lat': latitude,
+ 'lon': longitude,
+ 'start': start.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'end': end.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'surface_tilt': surface_tilt,
+ 'surface_azimuth': surface_azimuth,
+ 'horizon': horizon,
+ 'parameters': parameters,
+ }
+
+ if 'forecast' not in endpoint.lower():
+ params['frequency'] = time_step_map.get(time_step, time_step)
+
+ # convert list to string with values separated by commas
+ if not isinstance(params['parameters'], (str, type(None))):
+ # allow the use of pvlib parameter names
+ parameter_dict = {v: k for k, v in VARIABLE_MAP.items()}
+ parameters = [parameter_dict.get(p, p) for p in parameters]
+ params['parameters'] = ','.join(parameters)
+
+ headers = {"Authorization": f"Bearer {api_key}"}
+
+ response = requests.get(urljoin(url, endpoint), headers=headers, params=params)
+
+ if not response.ok:
+ # response.raise_for_status() does not give a useful error message
+ raise requests.HTTPError(response.json())
+
+ data_json = response.json()['values']
+ # identify empty columns
+ empty_columns = [k for k, v in data_json.items() if v is None]
+ # remove empty columns
+ _ = [data_json.pop(k) for k in empty_columns]
+
+ data = pd.DataFrame(data_json)
+
+ # xxx: experimental feature - see parameter description
+ if interval_index:
+ data.index = pd.IntervalIndex.from_arrays(
+ left=pd.to_datetime(response.json()['start_times']),
+ right=pd.to_datetime(response.json()['end_times']),
+ closed='both',
+ )
+ else:
+ data.index = pd.to_datetime(response.json()['start_times'])
+
+ meta = response.json()['meta']
+
+ if map_variables:
+ data = data.rename(columns=VARIABLE_MAP)
+ meta['latitude'] = meta.pop('lat')
+ meta['longitude'] = meta.pop('lon')
+
+ return data, meta
From 95c260245976c42dd9a62c372529d92f20f445bb Mon Sep 17 00:00:00 2001
From: "Adam R. Jensen" <39184289+AdamRJensen@users.noreply.github.com>
Date: Tue, 15 Jul 2025 13:07:02 +0200
Subject: [PATCH 2/3] Add get_meteonorm_tmy
---
docs/sphinx/source/reference/iotools.rst | 10 ++
docs/sphinx/source/whatsnew/v0.13.1.rst | 4 +-
pvlib/iotools/__init__.py | 2 +
pvlib/iotools/meteonorm.py | 208 +++++++++++++++++++++--
4 files changed, 205 insertions(+), 19 deletions(-)
diff --git a/docs/sphinx/source/reference/iotools.rst b/docs/sphinx/source/reference/iotools.rst
index cbf89c71a7..39ec0f70e9 100644
--- a/docs/sphinx/source/reference/iotools.rst
+++ b/docs/sphinx/source/reference/iotools.rst
@@ -81,6 +81,16 @@ Commercial datasets
Accessing these APIs typically requires payment.
Datasets provide near-global coverage.
+Meteonorm
+*********
+
+.. autosummary::
+ :toctree: generated/
+
+ iotools.get_meteonorm
+ iotools.get_meteonorm_tmy
+
+
SolarAnywhere
*************
diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst
index 9c50d00bbb..fd6b89cbcb 100644
--- a/docs/sphinx/source/whatsnew/v0.13.1.rst
+++ b/docs/sphinx/source/whatsnew/v0.13.1.rst
@@ -19,7 +19,9 @@ 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`.
+ (:pull:`2499`)
Documentation
~~~~~~~~~~~~~
diff --git a/pvlib/iotools/__init__.py b/pvlib/iotools/__init__.py
index 352044e5cd..e3ecd441cd 100644
--- a/pvlib/iotools/__init__.py
+++ b/pvlib/iotools/__init__.py
@@ -39,3 +39,5 @@
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_tmy # noqa: F401
diff --git a/pvlib/iotools/meteonorm.py b/pvlib/iotools/meteonorm.py
index d9897b8ceb..08a94ea2a5 100644
--- a/pvlib/iotools/meteonorm.py
+++ b/pvlib/iotools/meteonorm.py
@@ -29,7 +29,7 @@
def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
- parameters="all", *, surface_tilt=0, surface_azimuth=180,
+ parameters='all', *, surface_tilt=0, surface_azimuth=180,
time_step='15min', horizon='auto', interval_index=False,
map_variables=True, url=URL):
"""
@@ -38,9 +38,7 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
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 the end points 'realtime' for data for the past 7
- days, 'training' for historical data with a delay of 7 days. The function
- does not support TMY climate data.
+ This function supports both historical and forecast data, but not TMY.
Parameters
----------
@@ -57,15 +55,15 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
api_key: str
Meteonorm API key.
endpoint : str
- API end point, see [3]_. Must be one of:
+ API endpoint, see [3]_. Must be one of:
- * '/observation/training'
- * '/observation/realtime'
- * '/forecast/basic'
- * '/forecast/precision'
+ * '/observation/training' - historical data with a 7-day delay
+ * '/observation/realtime' - near-real time (past 7-days)
+ * '/forecast/basic' - forcasts with hourly resolution
+ * '/forecast/precision' - forecsat with 15-min resolution
parameters : list, optional
- List of parameters to request or "all" to get all parameters. The
+ List of parameters to request or 'all' to get all parameters. The
default is "all".
surface_tilt: float, default: 0
Tilt angle from horizontal plane.
@@ -73,12 +71,11 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north
(north=0, east=90, south=180, west=270).
time_step : {'1min', '15min', '1h'}, optional
- ime step of the time series. The default is '15min'. Ignored if
- requesting forecast data.
+ Frequency of the time series. The default is '15min'. The parameter is
+ ignored if forcasting data is requested.
horizon : optional
- Specification of the hoirzon line. Can be either 'flat' or 'auto', or
- specified as a list of 360 horizon elevation angles. The default is
- 'auto'.
+ Specification of the horizon line. Can be either a flat, 'auto', or
+ a list of 360 horizon elevation angles. The default is 'auto'.
interval_index: bool, optional
Whether the index of the returned data object is of the type
pd.DatetimeIndex or pd.IntervalIndex. This is an experimental feature
@@ -87,9 +84,10 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
When true, renames columns of the Dataframe to pvlib variable names
where applicable. The default is True. See variable
:const:`VARIABLE_MAP`.
- url: str, default: :const:`pvlib.iotools.meteonorm.URL`
+ url: str, optional
Base url of the Meteonorm API. The ``endpoint`` parameter is
- appended to the url.
+ appended to the url. The default is
+ :const:`pvlib.iotools.meteonorm.URL`.
Raises
------
@@ -145,7 +143,181 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
headers = {"Authorization": f"Bearer {api_key}"}
- response = requests.get(urljoin(url, endpoint), headers=headers, params=params)
+ response = requests.get(
+ urljoin(url, endpoint), headers=headers, params=params)
+
+ if not response.ok:
+ # response.raise_for_status() does not give a useful error message
+ raise requests.HTTPError(response.json())
+
+ data_json = response.json()['values']
+ # identify empty columns
+ empty_columns = [k for k, v in data_json.items() if v is None]
+ # remove empty columns
+ _ = [data_json.pop(k) for k in empty_columns]
+
+ data = pd.DataFrame(data_json)
+
+ # xxx: experimental feature - see parameter description
+ if interval_index:
+ data.index = pd.IntervalIndex.from_arrays(
+ left=pd.to_datetime(response.json()['start_times']),
+ right=pd.to_datetime(response.json()['end_times']),
+ closed='both',
+ )
+ else:
+ data.index = pd.to_datetime(response.json()['start_times'])
+
+ meta = response.json()['meta']
+
+ if map_variables:
+ data = data.rename(columns=VARIABLE_MAP)
+ meta['latitude'] = meta.pop('lat')
+ meta['longitude'] = meta.pop('lon')
+
+ return data, meta
+
+
+def get_meteonorm_tmy(latitude, longitude, api_key,
+ parameters='all', *, surface_tilt=0,
+ surface_azimuth=180, time_step='15min', horizon='auto',
+ terrain='open', albedo=0.2, turbidity='auto',
+ random_seed=None, clear_sky_radiation_model='esra',
+ data_version='latest', future_scenario=None,
+ future_year=None, interval_index=False,
+ map_variables=True, url=URL):
+ """
+ Retrieve irradiance and weather 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 the endpoints 'realtime' for data for the past 7
+ days, 'training' for historical data with a delay of 7 days. The function
+ does not support TMY climate data.
+
+ Parameters
+ ----------
+ latitude: float
+ In decimal degrees, north is positive (ISO 19115).
+ longitude: float
+ In decimal degrees, east is positive (ISO 19115).
+ api_key: str
+ Meteonorm API key.
+ parameters: list, optional
+ List of parameters to request or 'all' to get all parameters. The
+ default is 'all'.
+ 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', '1h'}, optional
+ Frequency of the time series. The default is '1h'.
+ horizon: optional
+ Specification of the hoirzon line. Can be either 'flat' or 'auto', or
+ specified as a list of 360 horizon elevation angles. The default is
+ 'auto'.
+ terrain: string, optional
+ Local terrain situation. Must be one of: ['open', 'depression',
+ 'cold_air_lake', 'sea_lake', 'city', 'slope_south',
+ 'slope_west_east']. The default is 'open'.
+ albedo: float, optional
+ Ground albedo. Albedo changes due to snow fall are modelled. The
+ default is 0.2.
+ turbidity: list or 'auto', optional
+ List of 12 monthly mean atmospheric Linke turbidity values. The default
+ is 'auto'.
+ random_seed: int, optional
+ Random seed to be used for stochastic processes. Two identical requests
+ with the same random seed will yield identical results.
+ clear_sky_radiation_model : {'esra', 'solis'}
+ Which clearsky model to use. The default is 'esra'.
+ data_version : string, optional
+ Version of Meteonorm climatological data to be used. The default is
+ 'latest'.
+ future_scenario: string, optional
+ Future climate scenario.
+ future_year : integer, optional
+ Central year for a 20-year reference period in the future.
+ interval_index: bool, optional
+ Whether the index of the returned data object is of the type
+ pd.DatetimeIndex or pd.IntervalIndex. This is an experimental feature
+ which may be removed without warning. The default is False.
+ map_variables: bool, default: True
+ When true, renames columns of the Dataframe to pvlib variable names
+ where applicable. The default is True. See variable
+ :const:`VARIABLE_MAP`.
+ url: str, optional.
+ Base url of the Meteonorm API. 'climate/tmy'` is
+ appended to the url. 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.
+ meta : dict
+ Metadata.
+
+ See Also
+ --------
+ pvlib.iotools.get_meteonorm
+
+ References
+ ----------
+ .. [1] `Meteonorm
+ `_
+ .. [2] `Meteonorm API
+ `_
+ .. [3] `Meteonorm API reference
+ `_
+ """
+ params = {
+ 'lat': latitude,
+ 'lon': longitude,
+ 'surface_tilt': surface_tilt,
+ 'surface_azimuth': surface_azimuth,
+ 'frequency': time_step,
+ 'parameters': parameters,
+ 'horizon': horizon,
+ 'terrain': terrain,
+ 'turbidity': turbidity,
+ 'clear_sky_radiation_model': clear_sky_radiation_model,
+ 'data_version': data_version,
+ }
+
+ if turbidity != 'auto':
+ params['turbidity'] = ','.join(turbidity)
+
+ if random_seed is not None:
+ params['random_seed'] = random_seed
+
+ if future_scenario is not None:
+ params['future_scenario'] = future_scenario
+
+ if future_year is not None:
+ params['future_year'] = future_year
+
+ # convert list to string with values separated by commas
+ if not isinstance(params['parameters'], (str, type(None))):
+ # allow the use of pvlib parameter names
+ parameter_dict = {v: k for k, v in VARIABLE_MAP.items()}
+ parameters = [parameter_dict.get(p, p) for p in parameters]
+ params['parameters'] = ','.join(parameters)
+
+ headers = {"Authorization": f"Bearer {api_key}"}
+
+ endpoint = 'climate/tmy'
+
+ response = requests.get(
+ urljoin(url, endpoint), headers=headers, params=params)
if not response.ok:
# response.raise_for_status() does not give a useful error message
From 611294ec7909345c2314a4f80d4c2c23e08721e5 Mon Sep 17 00:00:00 2001
From: "Adam R. Jensen" <39184289+AdamRJensen@users.noreply.github.com>
Date: Wed, 16 Jul 2025 13:41:21 +0200
Subject: [PATCH 3/3] Add private shared parse function
---
pvlib/iotools/meteonorm.py | 126 +++++++++++++++++--------------------
1 file changed, 56 insertions(+), 70 deletions(-)
diff --git a/pvlib/iotools/meteonorm.py b/pvlib/iotools/meteonorm.py
index 08a94ea2a5..33a4e4dc9d 100644
--- a/pvlib/iotools/meteonorm.py
+++ b/pvlib/iotools/meteonorm.py
@@ -1,4 +1,4 @@
-"""Functions for reading and retrieving data from Meteonorm."""
+"""Functions for retrieving data from Meteonorm."""
import pandas as pd
import requests
@@ -38,7 +38,7 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
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 both historical and forecast data, but not TMY.
+ This function supports historical and forecast data, but not TMY.
Parameters
----------
@@ -57,35 +57,35 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
endpoint : str
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' - forcasts with hourly resolution
- * '/forecast/precision' - forecsat with 15-min resolution
+ * ``'/observation/training'`` - historical data with a 7-day delay
+ * ``'/observation/realtime'`` - near-real time (past 7-days)
+ * ``'/forecast/basic'`` - forcast with hourly resolution
+ * ``'/forecast/precision'`` - forecast with 15-min resolution
parameters : list, optional
List of parameters to request or 'all' to get all parameters. The
- default is "all".
- surface_tilt: float, default: 0
- Tilt angle from horizontal plane.
- surface_azimuth: float, default: 180
+ default is 'all'.
+ surface_tilt: float, optional
+ Tilt angle from horizontal plane. The default is 0.
+ surface_azimuth: float, optional
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north
- (north=0, east=90, south=180, west=270).
+ (north=0, east=90, south=180, west=270). The default is 180.
time_step : {'1min', '15min', '1h'}, optional
- Frequency of the time series. The default is '15min'. The parameter is
- ignored if forcasting data is requested.
+ Frequency of the time series. The parameter is ignored when requesting
+ forcasting data. The default is '15min'.
horizon : optional
- Specification of the horizon line. Can be either a flat, 'auto', or
+ Specification of the horizon line. Can be either a 'flat', 'auto', or
a list of 360 horizon elevation angles. The default is 'auto'.
interval_index: bool, optional
Whether the index of the returned data object is of the type
pd.DatetimeIndex or pd.IntervalIndex. This is an experimental feature
which may be removed without warning. The default is False.
- map_variables: bool, default: True
+ map_variables: bool, optional
When true, renames columns of the Dataframe to pvlib variable names
where applicable. The default is True. See variable
:const:`VARIABLE_MAP`.
url: str, optional
- Base url of the Meteonorm API. The ``endpoint`` parameter is
+ Base URL of the Meteonorm API. The ``endpoint`` parameter is
appended to the url. The default is
:const:`pvlib.iotools.meteonorm.URL`.
@@ -98,7 +98,7 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
-------
data : pd.DataFrame
Time series data. The index corresponds to the start (left) of the
- interval.
+ interval unless ``interval_index`` is set to False.
meta : dict
Metadata.
@@ -125,15 +125,12 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
'lon': longitude,
'start': start.strftime('%Y-%m-%dT%H:%M:%SZ'),
'end': end.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'parameters': parameters,
'surface_tilt': surface_tilt,
'surface_azimuth': surface_azimuth,
'horizon': horizon,
- 'parameters': parameters,
}
- if 'forecast' not in endpoint.lower():
- params['frequency'] = time_step_map.get(time_step, time_step)
-
# convert list to string with values separated by commas
if not isinstance(params['parameters'], (str, type(None))):
# allow the use of pvlib parameter names
@@ -141,41 +138,27 @@ def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
parameters = [parameter_dict.get(p, p) for p in parameters]
params['parameters'] = ','.join(parameters)
+ if horizon not in ['auto', 'flat']:
+ params['horizon'] = ','.join(horizon)
+
+ if 'forecast' not in endpoint.lower():
+ params['frequency'] = time_step_map.get(time_step, time_step)
+
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(
urljoin(url, endpoint), headers=headers, params=params)
-
+ print(response)
if not response.ok:
# response.raise_for_status() does not give a useful error message
raise requests.HTTPError(response.json())
- data_json = response.json()['values']
- # identify empty columns
- empty_columns = [k for k, v in data_json.items() if v is None]
- # remove empty columns
- _ = [data_json.pop(k) for k in empty_columns]
-
- data = pd.DataFrame(data_json)
+ data, meta = _parse_meteonorm(response, interval_index, map_variables)
- # xxx: experimental feature - see parameter description
- if interval_index:
- data.index = pd.IntervalIndex.from_arrays(
- left=pd.to_datetime(response.json()['start_times']),
- right=pd.to_datetime(response.json()['end_times']),
- closed='both',
- )
- else:
- data.index = pd.to_datetime(response.json()['start_times'])
-
- meta = response.json()['meta']
+ return data, meta
- if map_variables:
- data = data.rename(columns=VARIABLE_MAP)
- meta['latitude'] = meta.pop('lat')
- meta['longitude'] = meta.pop('lon')
- return data, meta
+TMY_ENDPOINT = 'climate/tmy'
def get_meteonorm_tmy(latitude, longitude, api_key,
@@ -187,15 +170,11 @@ def get_meteonorm_tmy(latitude, longitude, api_key,
future_year=None, interval_index=False,
map_variables=True, url=URL):
"""
- Retrieve irradiance and weather data from Meteonorm.
+ Retrieve TMY irradiance and weather 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 the endpoints 'realtime' for data for the past 7
- days, 'training' for historical data with a delay of 7 days. The function
- does not support TMY climate data.
-
Parameters
----------
latitude: float
@@ -207,11 +186,11 @@ def get_meteonorm_tmy(latitude, longitude, api_key,
parameters: list, optional
List of parameters to request or 'all' to get all parameters. The
default is 'all'.
- surface_tilt: float, default: 0
- Tilt angle from horizontal plane.
- surface_azimuth : float, default: 180
+ surface_tilt: float, optional
+ Tilt angle from horizontal plane. The default is 0.
+ surface_azimuth : float, optional
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north
- (north=0, east=90, south=180, west=270).
+ (north=0, east=90, south=180, west=270). The default is 180.
time_step: {'1min', '1h'}, optional
Frequency of the time series. The default is '1h'.
horizon: optional
@@ -244,13 +223,13 @@ def get_meteonorm_tmy(latitude, longitude, api_key,
Whether the index of the returned data object is of the type
pd.DatetimeIndex or pd.IntervalIndex. This is an experimental feature
which may be removed without warning. The default is False.
- map_variables: bool, default: True
+ map_variables: bool, optional
When true, renames columns of the Dataframe to pvlib variable names
- where applicable. The default is True. See variable
- :const:`VARIABLE_MAP`.
+ where applicable. See variable :const:`VARIABLE_MAP`. The default is
+ True.
url: str, optional.
- Base url of the Meteonorm API. 'climate/tmy'` is
- appended to the url. The default is:
+ Base URL of the Meteonorm API. 'climate/tmy'` is
+ appended to the URL. The default is:
:const:`pvlib.iotools.meteonorm.URL`.
Raises
@@ -262,7 +241,7 @@ def get_meteonorm_tmy(latitude, longitude, api_key,
-------
data : pd.DataFrame
Time series data. The index corresponds to the start (left) of the
- interval.
+ interval unless ``interval_index`` is set to False.
meta : dict
Metadata.
@@ -293,6 +272,16 @@ def get_meteonorm_tmy(latitude, longitude, api_key,
'data_version': data_version,
}
+ # convert list to string with values separated by commas
+ if not isinstance(params['parameters'], (str, type(None))):
+ # allow the use of pvlib parameter names
+ parameter_dict = {v: k for k, v in VARIABLE_MAP.items()}
+ parameters = [parameter_dict.get(p, p) for p in parameters]
+ params['parameters'] = ','.join(parameters)
+
+ if horizon not in ['auto', 'flat']:
+ params['horizon'] = ','.join(horizon)
+
if turbidity != 'auto':
params['turbidity'] = ','.join(turbidity)
@@ -305,24 +294,21 @@ def get_meteonorm_tmy(latitude, longitude, api_key,
if future_year is not None:
params['future_year'] = future_year
- # convert list to string with values separated by commas
- if not isinstance(params['parameters'], (str, type(None))):
- # allow the use of pvlib parameter names
- parameter_dict = {v: k for k, v in VARIABLE_MAP.items()}
- parameters = [parameter_dict.get(p, p) for p in parameters]
- params['parameters'] = ','.join(parameters)
-
headers = {"Authorization": f"Bearer {api_key}"}
- endpoint = 'climate/tmy'
-
response = requests.get(
- urljoin(url, endpoint), headers=headers, params=params)
+ urljoin(url, TMY_ENDPOINT), headers=headers, params=params)
if not response.ok:
# response.raise_for_status() does not give a useful error message
raise requests.HTTPError(response.json())
+ data, meta = _parse_meteonorm(response, interval_index, map_variables)
+
+ return data, meta
+
+
+def _parse_meteonorm(response, interval_index, map_variables):
data_json = response.json()['values']
# identify empty columns
empty_columns = [k for k, v in data_json.items() if v is None]