Skip to content

Commit 7ca85bb

Browse files
committed
Create meteonorm.py
1 parent 908d3da commit 7ca85bb

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

pvlib/iotools/meteonorm.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"""Functions for reading and retrieving data from Meteonorm."""
2+
3+
import pandas as pd
4+
import requests
5+
from urllib.parse import urljoin
6+
7+
URL = 'https://api.meteonorm.com/v1/'
8+
9+
VARIABLE_MAP = {
10+
'global_horizontal_irradiance': 'ghi',
11+
'diffuse_horizontal_irradiance': 'dhi',
12+
'direct_normal_irradiance': 'dni',
13+
'direct_horizontal_irradiance': 'bhi',
14+
'global_clear_sky_irradiance': 'ghi_clear',
15+
'diffuse_tilted_irradiance': 'poa_diffuse',
16+
'direct_tilted_irradiance': 'poa_direct',
17+
'global_tilted_irradiance': 'poa',
18+
'temperature': 'temp_air',
19+
'dew_point_temperature': 'temp_dew',
20+
}
21+
22+
time_step_map = {
23+
'1h': '1_hour',
24+
'h': '1_hour',
25+
'15min': '15_minutes',
26+
'1min': '1_minute',
27+
'min': '1_minute',
28+
}
29+
30+
31+
def get_meteonorm(latitude, longitude, start, end, api_key, endpoint,
32+
parameters="all", *, surface_tilt=0, surface_azimuth=180,
33+
time_step='15min', horizon='auto', interval_index=False,
34+
map_variables=True, url=URL):
35+
"""
36+
Retrieve irradiance and weather data from Meteonorm.
37+
38+
The Meteonorm data options are described in [1]_ and the API is described
39+
in [2]_. A detailed list of API options can be found in [3]_.
40+
41+
This function supports the end points 'realtime' for data for the past 7
42+
days, 'training' for historical data with a delay of 7 days. The function
43+
does not support TMY climate data.
44+
45+
Parameters
46+
----------
47+
latitude: float
48+
In decimal degrees, north is positive (ISO 19115).
49+
longitude: float
50+
In decimal degrees, east is positive (ISO 19115).
51+
start: datetime like, optional
52+
First timestamp of the requested period. If a timezone is not
53+
specified, UTC is assumed. A relative datetime string is also allowed.
54+
end: datetime like, optional
55+
Last timestamp of the requested period. If a timezone is not
56+
specified, UTC is assumed. A relative datetime string is also allowed.
57+
api_key: str
58+
Meteonorm API key.
59+
endpoint : str
60+
API end point, see [3]_. Must be one of:
61+
62+
* '/observation/training'
63+
* '/observation/realtime'
64+
* '/forecast/basic'
65+
* '/forecast/precision'
66+
67+
parameters : list, optional
68+
List of parameters to request or "all" to get all parameters. The
69+
default is "all".
70+
surface_tilt: float, default: 0
71+
Tilt angle from horizontal plane.
72+
surface_azimuth: float, default: 180
73+
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north
74+
(north=0, east=90, south=180, west=270).
75+
time_step : {'1min', '15min', '1h'}, optional
76+
ime step of the time series. The default is '15min'. Ignored if
77+
requesting forecast data.
78+
horizon : optional
79+
Specification of the hoirzon line. Can be either 'flat' or 'auto', or
80+
specified as a list of 360 horizon elevation angles. The default is
81+
'auto'.
82+
interval_index: bool, optional
83+
Whether the index of the returned data object is of the type
84+
pd.DatetimeIndex or pd.IntervalIndex. This is an experimental feature
85+
which may be removed without warning. The default is False.
86+
map_variables: bool, default: True
87+
When true, renames columns of the Dataframe to pvlib variable names
88+
where applicable. The default is True. See variable
89+
:const:`VARIABLE_MAP`.
90+
url: str, default: :const:`pvlib.iotools.meteonorm.URL`
91+
Base url of the Meteonorm API. The ``endpoint`` parameter is
92+
appended to the url.
93+
94+
Raises
95+
------
96+
requests.HTTPError
97+
Raises an error when an incorrect request is made.
98+
99+
Returns
100+
-------
101+
data : pd.DataFrame
102+
Time series data. The index corresponds to the start (left) of the
103+
interval.
104+
meta : dict
105+
Metadata.
106+
107+
See Also
108+
--------
109+
pvlib.iotools.get_meteonorm_tmy
110+
111+
References
112+
----------
113+
.. [1] `Meteonorm
114+
<https://meteonorm.com/>`_
115+
.. [2] `Meteonorm API
116+
<https://docs.meteonorm.com/docs/getting-started>`_
117+
.. [3] `Meteonorm API reference
118+
<https://docs.meteonorm.com/api>`_
119+
"""
120+
start = pd.Timestamp(start)
121+
end = pd.Timestamp(end)
122+
start = start.tz_localize('UTC') if start.tzinfo is None else start
123+
end = end.tz_localize('UTC') if end.tzinfo is None else end
124+
125+
params = {
126+
'lat': latitude,
127+
'lon': longitude,
128+
'start': start.strftime('%Y-%m-%dT%H:%M:%SZ'),
129+
'end': end.strftime('%Y-%m-%dT%H:%M:%SZ'),
130+
'surface_tilt': surface_tilt,
131+
'surface_azimuth': surface_azimuth,
132+
'horizon': horizon,
133+
'parameters': parameters,
134+
}
135+
136+
if 'forecast' not in endpoint.lower():
137+
params['frequency'] = time_step_map.get(time_step, time_step)
138+
139+
# convert list to string with values separated by commas
140+
if not isinstance(params['parameters'], (str, type(None))):
141+
# allow the use of pvlib parameter names
142+
parameter_dict = {v: k for k, v in VARIABLE_MAP.items()}
143+
parameters = [parameter_dict.get(p, p) for p in parameters]
144+
params['parameters'] = ','.join(parameters)
145+
146+
headers = {"Authorization": f"Bearer {api_key}"}
147+
148+
response = requests.get(urljoin(url, endpoint), headers=headers, params=params)
149+
150+
if not response.ok:
151+
# response.raise_for_status() does not give a useful error message
152+
raise requests.HTTPError(response.json())
153+
154+
data_json = response.json()['values']
155+
# identify empty columns
156+
empty_columns = [k for k, v in data_json.items() if v is None]
157+
# remove empty columns
158+
_ = [data_json.pop(k) for k in empty_columns]
159+
160+
data = pd.DataFrame(data_json)
161+
162+
# xxx: experimental feature - see parameter description
163+
if interval_index:
164+
data.index = pd.IntervalIndex.from_arrays(
165+
left=pd.to_datetime(response.json()['start_times']),
166+
right=pd.to_datetime(response.json()['end_times']),
167+
closed='both',
168+
)
169+
else:
170+
data.index = pd.to_datetime(response.json()['start_times'])
171+
172+
meta = response.json()['meta']
173+
174+
if map_variables:
175+
data = data.rename(columns=VARIABLE_MAP)
176+
meta['latitude'] = meta.pop('lat')
177+
meta['longitude'] = meta.pop('lon')
178+
179+
return data, meta

0 commit comments

Comments
 (0)