|
| 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