Skip to content

Commit c0fd312

Browse files
kandersolar feedback (#3)
* addressing feedback from Kandersolar
1 parent 6792ecb commit c0fd312

File tree

3 files changed

+141
-43
lines changed

3 files changed

+141
-43
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ Enhancements
1212
* :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance` and
1313
:py:func:`pvlib.bifacial.infinite_sheds.get_irradiance_poa` now include
1414
shaded fraction in returned variables. (:pull:`1871`)
15-
* Added :py:func:`pvlib.iotools.solcast.get_solcast_tmy`, :py:func:`pvlib.iotools.solcast.get_solcast_historic`,
16-
:py:func:`pvlib.iotools.solcast.get_solcast_forecast` and :py:func:`pvlib.iotools.solcast.get_solcast_live` to
15+
* Added :py:func:`~pvlib.iotools.get_solcast_tmy`, :py:func:`~pvlib.iotools.get_solcast_historic`,
16+
:py:func:`~pvlib.iotools.get_solcast_forecast` and :py:func:`~pvlib.iotools.get_solcast_live` to
1717
read data from the Solcast API. (:issue:`1313`, :pull:`1875`)
1818

1919
Bug fixes

pvlib/iotools/solcast.py

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ParameterMap:
3232
ParameterMap("wind_direction_10m", "wind_direction"),
3333
# azimuth -> solar_azimuth (degrees) (different convention)
3434
ParameterMap(
35-
"azimuth", "solar_azimuth", lambda x: abs(x) if x <= 0 else 360 - x
35+
"azimuth", "solar_azimuth", lambda x: -x % 360
3636
),
3737
# precipitable_water (kg/m2) -> precipitable_water (cm)
3838
ParameterMap("precipitable_water", "precipitable_water", lambda x: x*10),
@@ -44,12 +44,12 @@ class ParameterMap:
4444
def get_solcast_tmy(
4545
latitude, longitude, api_key, map_variables=True, **kwargs
4646
):
47-
"""Get the irradiance and weather for a
47+
"""Get irradiance and weather for a
4848
Typical Meteorological Year (TMY) at a requested location.
4949
50-
Derived from satellite (clouds and irradiance over
51-
non-polar continental areas) and numerical weather models (other data).
52-
The TMY is calculated with data from 2007 to 2023.
50+
Data is derived from a multi-year time series selected to present the
51+
unique weather phenomena with annual averages that are consistent with
52+
long term averages. See [1]_ for details on the calculation.
5353
5454
Parameters
5555
----------
@@ -58,23 +58,25 @@ def get_solcast_tmy(
5858
longitude : float
5959
in decimal degrees, between -180 and 180, east is positive
6060
api_key : str
61-
To access Solcast data you will need an API key [1]_.
61+
To access Solcast data you will need an API key [2]_.
6262
map_variables: bool, default: True
6363
When true, renames columns of the DataFrame to pvlib variable names
6464
where applicable. See variable :const:`VARIABLE_MAP`.
6565
kwargs:
6666
Optional parameters passed to the API.
67-
See [2]_ for full list of parameters.
67+
See [3]_ for full list of parameters.
6868
6969
Returns
7070
-------
7171
data : pandas.DataFrame
7272
containing the values for the parameters requested.The times
7373
in the DataFrame index indicate the midpoint of each interval.
74+
metadata: dict
75+
latitude and longitude of the request.
7476
7577
Examples
7678
--------
77-
>>> pvlib.iotools.solcast.get_solcast_tmy(
79+
>>> df, meta = pvlib.iotools.solcast.get_solcast_tmy(
7880
>>> latitude=-33.856784,
7981
>>> longitude=151.215297,
8082
>>> api_key="your-key"
@@ -84,7 +86,7 @@ def get_solcast_tmy(
8486
like ``time_zone``. Here we set the value of 10 for
8587
"10 hours ahead of UTC":
8688
87-
>>> pvlib.iotools.solcast.get_solcast_tmy(
89+
>>> df, meta = pvlib.iotools.solcast.get_solcast_tmy(
8890
>>> latitude=-33.856784,
8991
>>> longitude=151.215297,
9092
>>> time_zone=10,
@@ -93,8 +95,9 @@ def get_solcast_tmy(
9395
9496
References
9597
----------
96-
.. [1] `Get an API Key <https://toolkit.solcast.com.au/register>`_
98+
.. [1] `Solcast TMY Docs <https://solcast.com/tmy>`_
9799
.. [2] `Solcast API Docs <https://docs.solcast.com.au/>`_
100+
.. [3] `Get an API Key <https://toolkit.solcast.com.au/register>`_
98101
"""
99102

100103
params = dict(
@@ -111,7 +114,7 @@ def get_solcast_tmy(
111114
map_variables=map_variables
112115
)
113116

114-
return data, {}
117+
return data, {"latitude": latitude, "longitude": longitude}
115118

116119

117120
def get_solcast_historic(
@@ -143,31 +146,31 @@ def get_solcast_historic(
143146
end : optional, datetime-like
144147
Last day of the requested period.
145148
Must include one of ``end`` or ``duration``.
146-
147149
duration : optional, default is None
148150
Must include either ``end`` or ``duration``.
149151
ISO_8601 compliant duration for the historic data,
150152
like "P1D" for one day of data.
151-
Must be within 31 days of the start_date.
153+
Must be within 31 days of the ``start``.
152154
map_variables: bool, default: True
153155
When true, renames columns of the DataFrame to pvlib variable names
154156
where applicable. See variable :const:`VARIABLE_MAP`.
155157
api_key : str
156158
To access Solcast data you will need an API key [1]_.
157159
kwargs:
158-
Optional parameters passed to the GET request
159-
160-
See [2]_ for full list of parameters.
160+
Optional parameters passed to the API.
161+
See [2]_ for full list of parameters.
161162
162163
Returns
163164
-------
164165
data : pandas.DataFrame
165166
containing the values for the parameters requested.The times
166167
in the DataFrame index indicate the midpoint of each interval.
168+
metadata: dict
169+
latitude and longitude of the request.
167170
168171
Examples
169172
--------
170-
>>> pvlib.iotools.solcast.get_solcast_historic(
173+
>>> df, meta = pvlib.iotools.solcast.get_solcast_historic(
171174
>>> latitude=-33.856784,
172175
>>> longitude=151.215297,
173176
>>> start='2007-01-01T00:00Z',
@@ -178,7 +181,7 @@ def get_solcast_historic(
178181
you can pass any of the parameters listed in the API docs,
179182
for example using the ``end`` parameter instead
180183
181-
>>> pvlib.iotools.solcast.get_solcast_historic(
184+
>>> df, meta = pvlib.iotools.solcast.get_solcast_historic(
182185
>>> latitude=-33.856784,
183186
>>> longitude=151.215297,
184187
>>> start='2007-01-01T00:00Z',
@@ -210,7 +213,7 @@ def get_solcast_historic(
210213
map_variables=map_variables
211214
)
212215

213-
return data, {}
216+
return data, {"latitude": latitude, "longitude": longitude}
214217

215218

216219
def get_solcast_forecast(
@@ -231,19 +234,20 @@ def get_solcast_forecast(
231234
When true, renames columns of the DataFrame to pvlib variable names
232235
where applicable. See variable :const:`VARIABLE_MAP`.
233236
kwargs:
234-
Optional parameters passed to the GET request
235-
236-
See [2]_ for full list of parameters.
237+
Optional parameters passed to the API.
238+
See [2]_ for full list of parameters.
237239
238240
Returns
239241
-------
240242
data : pandas.DataFrame
241243
Contains the values for the parameters requested.The times
242244
in the DataFrame index indicate the midpoint of each interval.
245+
metadata: dict
246+
latitude and longitude of the request.
243247
244248
Examples
245249
--------
246-
>>> pvlib.iotools.solcast.get_solcast_forecast(
250+
>>> df, meta = pvlib.iotools.solcast.get_solcast_forecast(
247251
>>> latitude=-33.856784,
248252
>>> longitude=151.215297,
249253
>>> api_key="your-key"
@@ -252,7 +256,7 @@ def get_solcast_forecast(
252256
you can pass any of the parameters listed in the API docs,
253257
like asking for specific variables:
254258
255-
>>> pvlib.iotools.solcast.get_solcast_forecast(
259+
>>> df, meta = pvlib.iotools.solcast.get_solcast_forecast(
256260
>>> latitude=-33.856784,
257261
>>> longitude=151.215297,
258262
>>> output_parameters=['dni', 'clearsky_dni', 'snow_soiling_rooftop'],
@@ -279,7 +283,7 @@ def get_solcast_forecast(
279283
map_variables=map_variables
280284
)
281285

282-
return data, {}
286+
return data, {"latitude": latitude, "longitude": longitude}
283287

284288

285289
def get_solcast_live(
@@ -300,27 +304,28 @@ def get_solcast_live(
300304
When true, renames columns of the DataFrame to pvlib variable names
301305
where applicable. See variable :const:`VARIABLE_MAP`.
302306
kwargs:
303-
Optional parameters passed to the GET request
304-
305-
See [2]_ for full list of parameters.
307+
Optional parameters passed to the API.
308+
See [2]_ for full list of parameters.
306309
307310
Returns
308311
-------
309312
data : pandas.DataFrame
310313
containing the values for the parameters requested.The times
311314
in the DataFrame index indicate the midpoint of each interval.
315+
metadata: dict
316+
latitude and longitude of the request.
312317
313318
Examples
314319
--------
315-
>>> pvlib.iotools.solcast.get_solcast_live(
320+
>>> df, meta = pvlib.iotools.solcast.get_solcast_live(
316321
>>> latitude=-33.856784,
317322
>>> longitude=151.215297,
318323
>>> api_key="your-key"
319324
>>> )
320325
321326
you can pass any of the parameters listed in the API docs, like
322327
323-
>>> pvlib.iotools.solcast.get_solcast_live(
328+
>>> df, meta = pvlib.iotools.solcast.get_solcast_live(
324329
>>> latitude=-33.856784,
325330
>>> longitude=151.215297,
326331
>>> terrain_shading=True,
@@ -331,7 +336,7 @@ def get_solcast_live(
331336
use ``map_variables=False`` to avoid converting the data
332337
to PVLib's conventions.
333338
334-
>>> pvlib.iotools.solcast.get_solcast_live(
339+
>>> df, meta = pvlib.iotools.solcast.get_solcast_live(
335340
>>> latitude=-33.856784,
336341
>>> longitude=151.215297,
337342
>>> map_variables=False,
@@ -358,10 +363,10 @@ def get_solcast_live(
358363
map_variables=map_variables
359364
)
360365

361-
return data, {}
366+
return data, {"latitude": latitude, "longitude": longitude}
362367

363368

364-
def solcast2pvlib(data):
369+
def _solcast2pvlib(data):
365370
"""Formats the data from Solcast to PVLib's conventions.
366371
367372
Parameters
@@ -411,10 +416,10 @@ def _get_solcast(
411416
api_key : str
412417
To access Solcast data you will need an API key [1]_.
413418
map_variables: bool, default: True
414-
When true, renames columns of the DataFrame to pvlib variable names
419+
When true, renames columns of the DataFrame to PVLib's variable names
415420
where applicable. See variable :const:`VARIABLE_MAP`.
416-
Time is the index as midpoint of each interval.
417-
from Solcast's "period end".
421+
Time is the index as midpoint of each interval
422+
from Solcast's "period end" convention.
418423
419424
Returns
420425
-------
@@ -436,7 +441,7 @@ def _get_solcast(
436441
j = response.json()
437442
df = pd.DataFrame.from_dict(j[list(j.keys())[0]])
438443
if map_variables:
439-
return solcast2pvlib(df)
444+
return _solcast2pvlib(df)
440445
else:
441446
return df
442447
else:

pvlib/tests/iotools/test_solcast.py

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pandas as pd
22
from pvlib.iotools.solcast import (
3-
get_solcast_live, get_solcast_tmy, solcast2pvlib
3+
get_solcast_live, get_solcast_tmy, _solcast2pvlib, get_solcast_historic,
4+
get_solcast_forecast
45
)
56
import pytest
67

@@ -40,7 +41,7 @@ def test_get_solcast_live(
4041

4142
pd.testing.assert_frame_equal(
4243
function(**params)[0],
43-
solcast2pvlib(
44+
_solcast2pvlib(
4445
pd.DataFrame.from_dict(
4546
json_response[list(json_response.keys())[0]])
4647
)
@@ -82,7 +83,7 @@ def test_get_solcast_tmy(
8283

8384
pd.testing.assert_frame_equal(
8485
function(**params)[0],
85-
solcast2pvlib(
86+
_solcast2pvlib(
8687
pd.DataFrame.from_dict(
8788
json_response[list(json_response.keys())[0]])
8889
)
@@ -118,5 +119,97 @@ def test_get_solcast_tmy(
118119
)
119120
])
120121
def test_solcast2pvlib(in_df, out_df):
121-
df = solcast2pvlib(in_df)
122+
df = _solcast2pvlib(in_df)
122123
pd.testing.assert_frame_equal(df.astype(float), out_df.astype(float))
124+
125+
126+
@pytest.mark.parametrize("endpoint,function,params,json_response", [
127+
(
128+
"historic/radiation_and_weather",
129+
get_solcast_historic,
130+
dict(
131+
api_key="1234",
132+
latitude=-33.856784,
133+
longitude=51.215297,
134+
start="2023-01-01T08:00",
135+
duration="P1D",
136+
period="PT1H",
137+
output_parameters='dni'
138+
), {'estimated_actuals': [
139+
{'dni': 822, 'period_end': '2023-01-01T09:00:00.0000000Z',
140+
'period': 'PT60M'},
141+
{'dni': 918, 'period_end': '2023-01-01T10:00:00.0000000Z',
142+
'period': 'PT60M'},
143+
{'dni': 772, 'period_end': '2023-01-01T11:00:00.0000000Z',
144+
'period': 'PT60M'},
145+
{'dni': 574, 'period_end': '2023-01-01T12:00:00.0000000Z',
146+
'period': 'PT60M'},
147+
{'dni': 494, 'period_end': '2023-01-01T13:00:00.0000000Z',
148+
'period': 'PT60M'}
149+
]}
150+
),
151+
])
152+
def test_get_solcast_historic(
153+
requests_mock, endpoint, function, params, json_response
154+
):
155+
mock_url = f"https://api.solcast.com.au/data/{endpoint}?" \
156+
f"&latitude={params['latitude']}&" \
157+
f"longitude={params['longitude']}&format=json"
158+
159+
requests_mock.get(mock_url, json=json_response)
160+
161+
pd.testing.assert_frame_equal(
162+
function(**params)[0],
163+
_solcast2pvlib(
164+
pd.DataFrame.from_dict(
165+
json_response[list(json_response.keys())[0]]
166+
)
167+
)
168+
)
169+
170+
171+
@pytest.mark.parametrize("endpoint,function,params,json_response", [
172+
(
173+
"forecast/radiation_and_weather",
174+
get_solcast_forecast,
175+
dict(
176+
api_key="1234",
177+
latitude=-33.856784,
178+
longitude=51.215297,
179+
hours="5",
180+
period="PT1H",
181+
output_parameters='dni'
182+
), {
183+
'forecast': [
184+
{'dni': 0, 'period_end': '2023-12-13T01:00:00.0000000Z',
185+
'period': 'PT1H'},
186+
{'dni': 1, 'period_end': '2023-12-13T02:00:00.0000000Z',
187+
'period': 'PT1H'},
188+
{'dni': 2, 'period_end': '2023-12-13T03:00:00.0000000Z',
189+
'period': 'PT1H'},
190+
{'dni': 3, 'period_end': '2023-12-13T04:00:00.0000000Z',
191+
'period': 'PT1H'},
192+
{'dni': 4, 'period_end': '2023-12-13T05:00:00.0000000Z',
193+
'period': 'PT1H'},
194+
{'dni': 5, 'period_end': '2023-12-13T06:00:00.0000000Z',
195+
'period': 'PT1H'}
196+
]}
197+
),
198+
])
199+
def test_get_solcast_forecast(
200+
requests_mock, endpoint, function, params, json_response
201+
):
202+
mock_url = f"https://api.solcast.com.au/data/{endpoint}?" \
203+
f"&latitude={params['latitude']}&" \
204+
f"longitude={params['longitude']}&format=json"
205+
206+
requests_mock.get(mock_url, json=json_response)
207+
208+
pd.testing.assert_frame_equal(
209+
function(**params)[0],
210+
_solcast2pvlib(
211+
pd.DataFrame.from_dict(
212+
json_response[list(json_response.keys())[0]]
213+
)
214+
)
215+
)

0 commit comments

Comments
 (0)