Skip to content

Commit d7deb80

Browse files
author
AdamRJensen
committed
Add cams.get_cams_radiation function
1 parent 8b98768 commit d7deb80

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed

docs/sphinx/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ relevant to solar energy modeling.
484484
iotools.get_pvgis_tmy
485485
iotools.read_pvgis_tmy
486486
iotools.read_bsrn
487+
iotools.get_cams_mcclear
487488

488489
A :py:class:`~pvlib.location.Location` object may be created from metadata
489490
in some files.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ Enhancements
6464
~~~~~~~~~~~~
6565
* Add :func:`~pvlib.iotools.read_bsrn` for reading BSRN solar radiation data
6666
files. (:pull:`1145`, :issue:`1015`)
67+
* Add :func:`~pvlib.iotools.get_cams_radiation` for retrieving CAMS McClear
68+
clear-sky radiation time series.
69+
files. (:pull:`1145`, :issue:`1015`)
6770
* In :py:class:`~pvlib.modelchain.ModelChain`, attributes which contain
6871
output of models are now collected into ``ModelChain.results``.
6972
(:pull:`1076`, :issue:`1067`)

pvlib/iotools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
from pvlib.iotools.psm3 import parse_psm3 # noqa: F401
1515
from pvlib.iotools.pvgis import get_pvgis_tmy, read_pvgis_tmy # noqa: F401
1616
from pvlib.iotools.bsrn import read_bsrn # noqa: F401
17+
from pvlib.iotools.cams import get_cams_radiation # noqa: F401

pvlib/iotools/cams.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""Functions to access data from Copernicus Atmosphere Monitoring Service
2+
(CAMS) radiation service.
3+
.. codeauthor:: Adam R. Jensen<[email protected]>
4+
"""
5+
6+
import pandas as pd
7+
import requests
8+
import io
9+
10+
11+
MCCLEAR_COLUMNS = ['Observation period', 'TOA', 'Clear sky GHI',
12+
'Clear sky BHI', 'Clear sky DHI', 'Clear sky BNI']
13+
14+
MCCLEAR_VERBOSE_COLUMNS = ['sza', 'summer/winter split', 'tco3', 'tcwv',
15+
'AOD BC', 'AOD DU', 'AOD SS', 'AOD OR', 'AOD SU',
16+
'AOD NI', 'AOD AM', 'alpha', 'Aerosol type',
17+
'fiso', 'fvol', 'fgeo', 'albedo']
18+
19+
# Dictionary mapping CAMS MCCLEAR variables to pvlib names
20+
MCCLEAR_VARIABLE_MAP = {
21+
'TOA': 'ghi_extra',
22+
'Clear sky GHI': 'ghi_clear',
23+
'Clear sky BHI': 'bhi_clear',
24+
'Clear sky DHI': 'dhi_clear',
25+
'Clear sky BNI': 'dni_clear',
26+
'sza': 'solar_zenith',
27+
}
28+
29+
30+
# Dictionary mapping Python time steps to CAMS time step format
31+
TIME_STEPS = {'1min': 'PT01M', '15min': 'PT15M', '1h': 'PT01H', '1d': 'P01D',
32+
'1M': 'P01M'}
33+
34+
TIME_STEPS_HOURS = {'1min': 1/60, '15min': 15/60, '1h': 1, '1d': 24}
35+
36+
37+
def get_cams_mcclear(start_date, end_date, latitude, longitude, email,
38+
altitude=None, time_step='1h', time_ref='UT',
39+
integrated=False, label=None, verbose=False,
40+
map_variables=True, server='www.soda-is.com'):
41+
"""
42+
Retrieve time-series of clear-sky global, beam, and diffuse radiation
43+
anywhere in the world from CAMS McClear [1]_ using the WGET service [2]_.
44+
45+
46+
Geographical coverage: wordwide
47+
Time coverage: 2004-01-01 to two days ago
48+
Access: free, but requires registration, see [1]_
49+
Requests: max. 100 per day
50+
51+
52+
Parameters
53+
----------
54+
start_date: datetime like
55+
First day of the requested period
56+
end_date: datetime like
57+
Last day of the requested period
58+
latitude: float
59+
in decimal degrees, between -90 and 90, north is positive (ISO 19115)
60+
longitude : float
61+
in decimal degrees, between -180 and 180, east is positive (ISO 19115)
62+
altitude: float, default: None
63+
Altitude in meters. If None, then the altitude is determined from the
64+
NASA SRTM database
65+
email: str
66+
Email address linked to a SoDa account
67+
time_step: str, {'1min', '15min', '1h', '1d', '1M'}, default: '1h'
68+
Time step of the time series, either 1 minute, 15 minute, hourly,
69+
daily, or monthly.
70+
time_reference: str, {'UT', 'TST'}, default: 'UT'
71+
'UT' (universal time) or 'TST' (True Solar Time)
72+
integrated: boolean, default False
73+
Whether to return integrated irradiation values (Wh/m^2) from CAMS or
74+
average irradiance values (W/m^2) as is more commonly used
75+
label: {‘right’, ‘left’}, default: None
76+
Which bin edge label to label bucket with. The default is ‘left’ for
77+
all frequency offsets except for ‘M’ which has a default of ‘right’.
78+
verbose: boolean, default: False
79+
Verbose mode outputs additional parameters (aerosols). Only avaiable
80+
for 1 minute and universal time. See [1] for parameter description.
81+
map_variables: bool, default: True
82+
When true, renames columns of the Dataframe to pvlib variable names
83+
where applicable. See variable MCCLEAR_VARIABLE_MAP.
84+
server: str, default: 'www.soda-is.com'
85+
Main server (www.soda-is.com) or backup mirror server (pro.soda-is.com)
86+
87+
88+
Notes
89+
----------
90+
The returned data Dataframe includes the following fields:
91+
92+
======================= ====== ==========================================
93+
Key, mapped key Format Description
94+
======================= ====== ==========================================
95+
**Mapped field names are returned when the map_variables argument is True**
96+
--------------------------------------------------------------------------
97+
Observation period str Beginning/end of time period
98+
TOA, ghi_extra float Horizontal radiation at top of atmosphere
99+
Clear sky GHI, ghi_clear float Clear sky global radiation on horizontal
100+
Clear sky BHI, bhi_clear float Clear sky beam radiation on horizontal
101+
Clear sky DHI, dhi_clear float Clear sky diffuse radiation on horizontal
102+
Clear sky BNI, dni_clear float Clear sky beam radiation normal to sun
103+
======================= ====== ==========================================
104+
105+
For the returned units see the integrated argument. For description of
106+
additional output parameters in verbose mode, see [1].
107+
108+
Note that it is recommended to specify the latitude and longitude to at
109+
least the fourth decimal place.
110+
111+
Variables corresponding to standard pvlib variables are renamed,
112+
e.g. `sza` becomes `solar_zenith`. See the
113+
`pvlib.iotools.cams.MCCLEAR_VARIABLE_MAP` dict for the complete mapping.
114+
115+
116+
References
117+
----------
118+
.. [1] `CAMS McClear Service Info
119+
<http://www.soda-pro.com/web-services/radiation/cams-mcclear/info>`_
120+
.. [2] `CAMS McClear Automatic Access
121+
<http://www.soda-pro.com/help/cams-services/cams-mcclear-service/automatic-access>`_
122+
"""
123+
124+
if time_step in TIME_STEPS.keys():
125+
time_step_str = TIME_STEPS[time_step]
126+
else:
127+
print('WARNING: time step not recognized, 1 hour time step used!')
128+
time_step_str = 'PT01H'
129+
130+
names = MCCLEAR_COLUMNS
131+
if verbose:
132+
if (time_step == '1min') & (time_ref == 'UT'):
133+
names += MCCLEAR_VERBOSE_COLUMNS
134+
else:
135+
verbose = False
136+
print("Verbose mode only supports 1 min. UT time series!")
137+
138+
if altitude is None: # Let SoDa get elevation from the NASA SRTM database
139+
altitude = -999
140+
141+
# Start and end date should be in the format: yyyy-mm-dd
142+
start_date = start_date.strftime('%Y-%m-%d')
143+
end_date = end_date.strftime('%Y-%m-%d')
144+
145+
email = email.replace('@', '%2540') # Format email address
146+
147+
# Format verbose variable to the required format: {'true', 'false'}
148+
verbose = str(verbose).lower()
149+
150+
# Manual format the request url, due to uncommon usage of & and ; in url
151+
url = ("http://{}/service/wps?Service=WPS&Request=Execute&"
152+
"Identifier=get_mcclear&version=1.0.0&RawDataOutput=irradiation&"
153+
"DataInputs=latitude={};longitude={};altitude={};"
154+
"date_begin={};date_end={};time_ref={};summarization={};"
155+
"username={};verbose={}"
156+
).format(server, latitude, longitude, altitude, start_date,
157+
end_date, time_ref, time_step_str, email, verbose)
158+
159+
res = requests.get(url)
160+
161+
# Invalid requests returns helpful XML error message
162+
if res.headers['Content-Type'] == 'application/xml':
163+
print('REQUEST ERROR MESSAGE:')
164+
print(res.text.split('ows:ExceptionText')[1][1:-2])
165+
166+
# Check if returned file is a csv data file
167+
elif res.headers['Content-Type'] == 'application/csv':
168+
data = pd.read_csv(io.StringIO(res.content.decode('utf-8')), sep=';',
169+
comment='#', header=None, names=names)
170+
171+
obs_period = data['Observation period'].str.split('/')
172+
173+
# Set index as the start observation time (left) and localize to UTC
174+
if (label == 'left') | ((label is None) & (time_step != '1M')):
175+
data.index = pd.to_datetime(obs_period.str[0], utc=True)
176+
# Set index as the stop observation time (right) and localize to UTC
177+
elif (label == 'right') | ((label is None) & (time_step == '1M')):
178+
data.index = pd.to_datetime(obs_period.str[1], utc=True)
179+
180+
data.index.name = None # Set index name to None
181+
182+
# Change index for '1d' and '1M' to be date and not datetime
183+
if time_step == '1d':
184+
data.index = data.index.date
185+
elif (time_step == '1M') & (label is not None):
186+
data.index = data.index.date
187+
# For monthly data with 'right' label, the index should be the last
188+
# date of the month and not the first date of the following month
189+
elif (time_step == '1M') & (time_step != 'left'):
190+
data.index = data.index.date - pd.Timestamp(days=1)
191+
192+
if not integrated: # Convert from Wh/m2 to W/m2
193+
integrated_cols = MCCLEAR_COLUMNS[1:6]
194+
195+
if time_step == '1M':
196+
time_delta = (pd.to_datetime(obs_period.str[1])
197+
- pd.to_datetime(obs_period.str[0]))
198+
hours = time_delta.dt.total_seconds()/60/60
199+
data[integrated_cols] = data[integrated_cols] / hours
200+
else:
201+
data[integrated_cols] = (data[integrated_cols] /
202+
TIME_STEPS_HOURS[time_step])
203+
204+
if map_variables:
205+
data = data.rename(columns=MCCLEAR_VARIABLE_MAP)
206+
207+
return data

0 commit comments

Comments
 (0)