pvlib 0.12.0__py3-none-any.whl → 0.13.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pvlib/iotools/psm4.py ADDED
@@ -0,0 +1,766 @@
1
+ """
2
+ Functions for reading and retrieving data from NSRDB PSM4. See:
3
+ https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-aggregated-v4-0-0-download/
4
+ https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-tmy-v4-0-0-download/
5
+ https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-conus-v4-0-0-download/
6
+ https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-full-disc-v4-0-0-download/
7
+ """
8
+
9
+ import io
10
+ from urllib.parse import urljoin
11
+ import requests
12
+ import pandas as pd
13
+ from json import JSONDecodeError
14
+ from pvlib import tools
15
+
16
+ NSRDB_API_BASE = "https://developer.nrel.gov/api/nsrdb/v2/solar/"
17
+ PSM4_AGG_ENDPOINT = "nsrdb-GOES-aggregated-v4-0-0-download.csv"
18
+ PSM4_TMY_ENDPOINT = "nsrdb-GOES-tmy-v4-0-0-download.csv"
19
+ PSM4_CON_ENDPOINT = "nsrdb-GOES-conus-v4-0-0-download.csv"
20
+ PSM4_FUL_ENDPOINT = "nsrdb-GOES-full-disc-v4-0-0-download.csv"
21
+ PSM4_AGG_URL = urljoin(NSRDB_API_BASE, PSM4_AGG_ENDPOINT)
22
+ PSM4_TMY_URL = urljoin(NSRDB_API_BASE, PSM4_TMY_ENDPOINT)
23
+ PSM4_CON_URL = urljoin(NSRDB_API_BASE, PSM4_CON_ENDPOINT)
24
+ PSM4_FUL_URL = urljoin(NSRDB_API_BASE, PSM4_FUL_ENDPOINT)
25
+
26
+ PARAMETERS = (
27
+ 'air_temperature', 'dew_point', 'dhi', 'dni', 'ghi', 'surface_albedo',
28
+ 'surface_pressure', 'wind_direction', 'wind_speed')
29
+ PVLIB_PYTHON = 'pvlib python'
30
+
31
+ # Dictionary mapping PSM4 response names to pvlib names
32
+ VARIABLE_MAP = {
33
+ 'GHI': 'ghi',
34
+ 'DHI': 'dhi',
35
+ 'DNI': 'dni',
36
+ 'Clearsky GHI': 'ghi_clear',
37
+ 'Clearsky DHI': 'dhi_clear',
38
+ 'Clearsky DNI': 'dni_clear',
39
+ 'Solar Zenith Angle': 'solar_zenith',
40
+ 'Temperature': 'temp_air',
41
+ 'Dew Point': 'temp_dew',
42
+ 'Relative Humidity': 'relative_humidity',
43
+ 'Pressure': 'pressure',
44
+ 'Wind Speed': 'wind_speed',
45
+ 'Wind Direction': 'wind_direction',
46
+ 'Surface Albedo': 'albedo',
47
+ 'Precipitable Water': 'precipitable_water',
48
+ 'AOD': 'aod',
49
+ }
50
+
51
+ # Dictionary mapping pvlib names to PSM4 request names
52
+ # Note, PSM4 uses different names for the same variables in the
53
+ # response and the request
54
+ REQUEST_VARIABLE_MAP = {
55
+ 'ghi': 'ghi',
56
+ 'dhi': 'dhi',
57
+ 'dni': 'dni',
58
+ 'ghi_clear': 'clearsky_ghi',
59
+ 'dhi_clear': 'clearsky_dhi',
60
+ 'dni_clear': 'clearsky_dni',
61
+ 'solar_zenith': 'solar_zenith_angle',
62
+ 'temp_air': 'air_temperature',
63
+ 'temp_dew': 'dew_point',
64
+ 'relative_humidity': 'relative_humidity',
65
+ 'pressure': 'surface_pressure',
66
+ 'wind_speed': 'wind_speed',
67
+ 'wind_direction': 'wind_direction',
68
+ 'albedo': 'surface_albedo',
69
+ 'precipitable_water': 'total_precipitable_water',
70
+ 'aod': 'aod',
71
+ }
72
+
73
+
74
+ def get_nsrdb_psm4_aggregated(latitude, longitude, api_key, email,
75
+ year, time_step=60,
76
+ parameters=PARAMETERS, leap_day=True,
77
+ full_name=PVLIB_PYTHON,
78
+ affiliation=PVLIB_PYTHON,
79
+ utc=False, map_variables=True, url=None,
80
+ timeout=30):
81
+ """
82
+ Retrieve NSRDB PSM4 timeseries weather data from the PSM4 NSRDB GOES
83
+ Aggregated v4 API.
84
+
85
+ The NSRDB is described in [1]_ and the PSM4 NSRDB GOES Aggregated v4 API is
86
+ described in [2]_.
87
+
88
+ Parameters
89
+ ----------
90
+ latitude : float or int
91
+ in decimal degrees, between -90 and 90, north is positive
92
+ longitude : float or int
93
+ in decimal degrees, between -180 and 180, east is positive
94
+ api_key : str
95
+ NREL Developer Network API key
96
+ email : str
97
+ NREL API uses this to automatically communicate messages back
98
+ to the user only if necessary
99
+ year : int or str
100
+ PSM4 API parameter specifing year (e.g. ``2023``) to download. The
101
+ allowed values update periodically, so consult the NSRDB reference
102
+ below for the current set of options. Called ``names`` in NSRDB API.
103
+ time_step : int, {60, 30}
104
+ time step in minutes, must be 60 or 30 for PSM4 Aggregated. Called
105
+ ``interval`` in NSRDB API.
106
+ parameters : list of str, optional
107
+ meteorological fields to fetch. If not specified, defaults to
108
+ ``pvlib.iotools.psm4.PARAMETERS``. See reference [2]_ for a list of
109
+ available fields. Alternatively, pvlib names may also be used (e.g.
110
+ 'ghi' rather than 'GHI'); see :const:`REQUEST_VARIABLE_MAP`. To
111
+ retrieve all available fields, set ``parameters=[]``.
112
+ leap_day : bool, default : True
113
+ include leap day in the results
114
+ full_name : str, default 'pvlib python'
115
+ optional
116
+ affiliation : str, default 'pvlib python'
117
+ optional
118
+ utc: bool, default : False
119
+ retrieve data with timestamps converted to UTC. False returns
120
+ timestamps in local standard time of the selected location
121
+ map_variables : bool, default True
122
+ When true, renames columns of the Dataframe to pvlib variable names
123
+ where applicable. See variable :const:`VARIABLE_MAP`.
124
+ url : str, optional
125
+ Full API endpoint URL. If not specified, the PSM4 GOES Aggregated v4
126
+ URL is used.
127
+ timeout : int, default 30
128
+ time in seconds to wait for server response before timeout
129
+
130
+ Returns
131
+ -------
132
+ data : pandas.DataFrame
133
+ timeseries data from NREL PSM4
134
+ metadata : dict
135
+ metadata from NREL PSM4 about the record, see
136
+ :func:`pvlib.iotools.read_nsrdb_psm4` for fields
137
+
138
+ Raises
139
+ ------
140
+ requests.HTTPError
141
+ if the request response status is not ok, then the ``'errors'`` field
142
+ from the JSON response or any error message in the content will be
143
+ raised as an exception, for example if the `api_key` was rejected or if
144
+ the coordinates were not found in the NSRDB
145
+
146
+ Notes
147
+ -----
148
+ The required NREL developer key, `api_key`, is available for free by
149
+ registering at the `NREL Developer Network <https://developer.nrel.gov/>`_.
150
+
151
+ .. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
152
+ result in rejected requests.
153
+
154
+ .. warning:: PSM4 is limited to data found in the NSRDB, please consult
155
+ the references below for locations with available data.
156
+
157
+ See Also
158
+ --------
159
+ pvlib.iotools.get_nsrdb_psm4_tmy, pvlib.iotools.get_nsrdb_psm4_conus,
160
+ pvlib.iotools.get_nsrdb_psm4_full_disc, pvlib.iotools.read_nsrdb_psm4
161
+
162
+ References
163
+ ----------
164
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
165
+ <https://nsrdb.nrel.gov/>`_
166
+ .. [2] `NSRDB GOES Aggregated V4.0.0
167
+ <https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-aggregated-v4-0-0-download/>`_
168
+ """
169
+ # The well know text (WKT) representation of geometry notation is strict.
170
+ # A POINT object is a string with longitude first, then the latitude, with
171
+ # four decimals each, and exactly one space between them.
172
+ longitude = ('%9.4f' % longitude).strip()
173
+ latitude = ('%8.4f' % latitude).strip()
174
+ # TODO: make format_WKT(object_type, *args) in tools.py
175
+
176
+ # convert pvlib names in parameters to PSM4 convention
177
+ parameters = [REQUEST_VARIABLE_MAP.get(a, a) for a in parameters]
178
+
179
+ # required query-string parameters for request to PSM4 API
180
+ params = {
181
+ 'api_key': api_key,
182
+ 'full_name': full_name,
183
+ 'email': email,
184
+ 'affiliation': affiliation,
185
+ 'reason': PVLIB_PYTHON,
186
+ 'mailing_list': 'false',
187
+ 'wkt': 'POINT(%s %s)' % (longitude, latitude),
188
+ 'names': year,
189
+ 'attributes': ','.join(parameters),
190
+ 'leap_day': str(leap_day).lower(),
191
+ 'utc': str(utc).lower(),
192
+ 'interval': time_step
193
+ }
194
+ # request CSV download from NREL PSM4
195
+ if url is None:
196
+ url = PSM4_AGG_URL
197
+
198
+ response = requests.get(url, params=params, timeout=timeout)
199
+ if not response.ok:
200
+ # if the API key is rejected, then the response status will be 403
201
+ # Forbidden, and then the error is in the content and there is no JSON
202
+ try:
203
+ errors = response.json()['errors']
204
+ except JSONDecodeError:
205
+ errors = response.content.decode('utf-8')
206
+ raise requests.HTTPError(errors, response=response)
207
+ # the CSV is in the response content as a UTF-8 bytestring
208
+ # to use pandas we need to create a file buffer from the response
209
+ fbuf = io.StringIO(response.content.decode('utf-8'))
210
+ return read_nsrdb_psm4(fbuf, map_variables)
211
+
212
+
213
+ def get_nsrdb_psm4_tmy(latitude, longitude, api_key, email, year='tmy',
214
+ time_step=60, parameters=PARAMETERS, leap_day=False,
215
+ full_name=PVLIB_PYTHON, affiliation=PVLIB_PYTHON,
216
+ utc=False, map_variables=True, url=None, timeout=30):
217
+ """
218
+ Retrieve NSRDB PSM4 timeseries weather data from the PSM4 NSRDB GOES
219
+ TMY v4 API.
220
+
221
+ The NSRDB is described in [1]_ and the PSM4 NSRDB GOES TMY v4 API is
222
+ described in [2]_.
223
+
224
+ Parameters
225
+ ----------
226
+ latitude : float or int
227
+ in decimal degrees, between -90 and 90, north is positive
228
+ longitude : float or int
229
+ in decimal degrees, between -180 and 180, east is positive
230
+ api_key : str
231
+ NREL Developer Network API key
232
+ email : str
233
+ NREL API uses this to automatically communicate messages back
234
+ to the user only if necessary
235
+ year : str, default 'tmy'
236
+ PSM4 API parameter specifing TMY variant to download (e.g. ``'tmy'``
237
+ or ``'tgy-2022'``). The allowed values update periodically, so
238
+ consult the NSRDB references below for the current set of options.
239
+ Called ``names`` in NSRDB API.
240
+ time_step : int, {60}
241
+ time step in minutes. Must be 60 for typical year requests. Called
242
+ ``interval`` in NSRDB API.
243
+ parameters : list of str, optional
244
+ meteorological fields to fetch. If not specified, defaults to
245
+ ``pvlib.iotools.psm4.PARAMETERS``. See reference [2]_ for a list of
246
+ available fields. Alternatively, pvlib names may also be used (e.g.
247
+ 'ghi' rather than 'GHI'); see :const:`REQUEST_VARIABLE_MAP`. To
248
+ retrieve all available fields, set ``parameters=[]``.
249
+ leap_day : bool, default : False
250
+ Include leap day in the results. Ignored for tmy/tgy/tdy requests.
251
+ full_name : str, default 'pvlib python'
252
+ optional
253
+ affiliation : str, default 'pvlib python'
254
+ optional
255
+ utc: bool, default : False
256
+ retrieve data with timestamps converted to UTC. False returns
257
+ timestamps in local standard time of the selected location
258
+ map_variables : bool, default True
259
+ When true, renames columns of the Dataframe to pvlib variable names
260
+ where applicable. See variable :const:`VARIABLE_MAP`.
261
+ url : str, optional
262
+ Full API endpoint URL. If not specified, the PSM4 GOES TMY v4 URL is
263
+ used.
264
+ timeout : int, default 30
265
+ time in seconds to wait for server response before timeout
266
+
267
+ Returns
268
+ -------
269
+ data : pandas.DataFrame
270
+ timeseries data from NREL PSM4
271
+ metadata : dict
272
+ metadata from NREL PSM4 about the record, see
273
+ :func:`pvlib.iotools.read_nsrdb_psm4` for fields
274
+
275
+ Raises
276
+ ------
277
+ requests.HTTPError
278
+ if the request response status is not ok, then the ``'errors'`` field
279
+ from the JSON response or any error message in the content will be
280
+ raised as an exception, for example if the `api_key` was rejected or if
281
+ the coordinates were not found in the NSRDB
282
+
283
+ Notes
284
+ -----
285
+ The required NREL developer key, `api_key`, is available for free by
286
+ registering at the `NREL Developer Network <https://developer.nrel.gov/>`_.
287
+
288
+ .. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
289
+ result in rejected requests.
290
+
291
+ .. warning:: PSM4 is limited to data found in the NSRDB, please consult
292
+ the references below for locations with available data.
293
+
294
+ See Also
295
+ --------
296
+ pvlib.iotools.get_nsrdb_psm4_aggregated,
297
+ pvlib.iotools.get_nsrdb_psm4_conus, pvlib.iotools.get_nsrdb_psm4_full_disc,
298
+ pvlib.iotools.read_nsrdb_psm4
299
+
300
+ References
301
+ ----------
302
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
303
+ <https://nsrdb.nrel.gov/>`_
304
+ .. [2] `NSRDB GOES Tmy V4.0.0
305
+ <https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-tmy-v4-0-0-download/>`_
306
+ """
307
+ # The well know text (WKT) representation of geometry notation is strict.
308
+ # A POINT object is a string with longitude first, then the latitude, with
309
+ # four decimals each, and exactly one space between them.
310
+ longitude = ('%9.4f' % longitude).strip()
311
+ latitude = ('%8.4f' % latitude).strip()
312
+ # TODO: make format_WKT(object_type, *args) in tools.py
313
+
314
+ # convert pvlib names in parameters to PSM4 convention
315
+ parameters = [REQUEST_VARIABLE_MAP.get(a, a) for a in parameters]
316
+
317
+ # required query-string parameters for request to PSM4 API
318
+ params = {
319
+ 'api_key': api_key,
320
+ 'full_name': full_name,
321
+ 'email': email,
322
+ 'affiliation': affiliation,
323
+ 'reason': PVLIB_PYTHON,
324
+ 'mailing_list': 'false',
325
+ 'wkt': 'POINT(%s %s)' % (longitude, latitude),
326
+ 'names': year,
327
+ 'attributes': ','.join(parameters),
328
+ 'leap_day': str(leap_day).lower(),
329
+ 'utc': str(utc).lower(),
330
+ 'interval': time_step
331
+ }
332
+ # request CSV download from NREL PSM4
333
+ if url is None:
334
+ url = PSM4_TMY_URL
335
+
336
+ response = requests.get(url, params=params, timeout=timeout)
337
+ if not response.ok:
338
+ # if the API key is rejected, then the response status will be 403
339
+ # Forbidden, and then the error is in the content and there is no JSON
340
+ try:
341
+ errors = response.json()['errors']
342
+ except JSONDecodeError:
343
+ errors = response.content.decode('utf-8')
344
+ raise requests.HTTPError(errors, response=response)
345
+ # the CSV is in the response content as a UTF-8 bytestring
346
+ # to use pandas we need to create a file buffer from the response
347
+ fbuf = io.StringIO(response.content.decode('utf-8'))
348
+ return read_nsrdb_psm4(fbuf, map_variables)
349
+
350
+
351
+ def get_nsrdb_psm4_conus(latitude, longitude, api_key, email, year,
352
+ time_step=60, parameters=PARAMETERS, leap_day=True,
353
+ full_name=PVLIB_PYTHON, affiliation=PVLIB_PYTHON,
354
+ utc=False, map_variables=True, url=None, timeout=30):
355
+ """
356
+ Retrieve NSRDB PSM4 timeseries weather data from the PSM4 NSRDB GOES CONUS
357
+ v4 API.
358
+
359
+ The NSRDB is described in [1]_ and the PSM4 NSRDB GOES CONUS v4 API is
360
+ described in [2]_.
361
+
362
+ Parameters
363
+ ----------
364
+ latitude : float or int
365
+ in decimal degrees, between -90 and 90, north is positive
366
+ longitude : float or int
367
+ in decimal degrees, between -180 and 180, east is positive
368
+ api_key : str
369
+ NREL Developer Network API key
370
+ email : str
371
+ NREL API uses this to automatically communicate messages back
372
+ to the user only if necessary
373
+ year : int or str
374
+ PSM4 API parameter specifing year (e.g. ``2023``) to download. The
375
+ allowed values update periodically, so consult the NSRDB reference
376
+ below for the current set of options. Called ``names`` in NSRDB API.
377
+ time_step : int, {60, 5, 15, 30}
378
+ time step in minutes. Called ``interval`` in NSRDB API.
379
+ parameters : list of str, optional
380
+ meteorological fields to fetch. If not specified, defaults to
381
+ ``pvlib.iotools.psm4.PARAMETERS``. See reference [2]_ for a list of
382
+ available fields. Alternatively, pvlib names may also be used (e.g.
383
+ 'ghi' rather than 'GHI'); see :const:`REQUEST_VARIABLE_MAP`. To
384
+ retrieve all available fields, set ``parameters=[]``.
385
+ leap_day : bool, default : True
386
+ include leap day in the results
387
+ full_name : str, default 'pvlib python'
388
+ optional
389
+ affiliation : str, default 'pvlib python'
390
+ optional
391
+ utc: bool, default : False
392
+ retrieve data with timestamps converted to UTC. False returns
393
+ timestamps in local standard time of the selected location
394
+ map_variables : bool, default True
395
+ When true, renames columns of the Dataframe to pvlib variable names
396
+ where applicable. See variable :const:`VARIABLE_MAP`.
397
+ url : str, optional
398
+ Full API endpoint URL. If not specified, the PSM4 GOES CONUS v4 URL is
399
+ used.
400
+ timeout : int, default 30
401
+ time in seconds to wait for server response before timeout
402
+
403
+ Returns
404
+ -------
405
+ data : pandas.DataFrame
406
+ timeseries data from NREL PSM4
407
+ metadata : dict
408
+ metadata from NREL PSM4 about the record, see
409
+ :func:`pvlib.iotools.read_nsrdb_psm4` for fields
410
+
411
+ Raises
412
+ ------
413
+ requests.HTTPError
414
+ if the request response status is not ok, then the ``'errors'`` field
415
+ from the JSON response or any error message in the content will be
416
+ raised as an exception, for example if the `api_key` was rejected or if
417
+ the coordinates were not found in the NSRDB
418
+
419
+ Notes
420
+ -----
421
+ The required NREL developer key, `api_key`, is available for free by
422
+ registering at the `NREL Developer Network <https://developer.nrel.gov/>`_.
423
+
424
+ .. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
425
+ result in rejected requests.
426
+
427
+ .. warning:: PSM4 is limited to data found in the NSRDB, please consult
428
+ the references below for locations with available data.
429
+
430
+ See Also
431
+ --------
432
+ pvlib.iotools.get_nsrdb_psm4_aggregated,
433
+ pvlib.iotools.get_nsrdb_psm4_tmy, pvlib.iotools.get_nsrdb_psm4_full_disc,
434
+ pvlib.iotools.read_nsrdb_psm4
435
+
436
+ References
437
+ ----------
438
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
439
+ <https://nsrdb.nrel.gov/>`_
440
+ .. [2] `NSRDB GOES Conus V4.0.0
441
+ <https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-conus-v4-0-0-download/>`_
442
+ """
443
+ # The well know text (WKT) representation of geometry notation is strict.
444
+ # A POINT object is a string with longitude first, then the latitude, with
445
+ # four decimals each, and exactly one space between them.
446
+ longitude = ('%9.4f' % longitude).strip()
447
+ latitude = ('%8.4f' % latitude).strip()
448
+ # TODO: make format_WKT(object_type, *args) in tools.py
449
+
450
+ # convert pvlib names in parameters to PSM4 convention
451
+ parameters = [REQUEST_VARIABLE_MAP.get(a, a) for a in parameters]
452
+
453
+ # required query-string parameters for request to PSM4 API
454
+ params = {
455
+ 'api_key': api_key,
456
+ 'full_name': full_name,
457
+ 'email': email,
458
+ 'affiliation': affiliation,
459
+ 'reason': PVLIB_PYTHON,
460
+ 'mailing_list': 'false',
461
+ 'wkt': 'POINT(%s %s)' % (longitude, latitude),
462
+ 'names': year,
463
+ 'attributes': ','.join(parameters),
464
+ 'leap_day': str(leap_day).lower(),
465
+ 'utc': str(utc).lower(),
466
+ 'interval': time_step
467
+ }
468
+ # request CSV download from NREL PSM4
469
+ if url is None:
470
+ url = PSM4_CON_URL
471
+
472
+ response = requests.get(url, params=params, timeout=timeout)
473
+ if not response.ok:
474
+ # if the API key is rejected, then the response status will be 403
475
+ # Forbidden, and then the error is in the content and there is no JSON
476
+ try:
477
+ errors = response.json()['errors']
478
+ except JSONDecodeError:
479
+ errors = response.content.decode('utf-8')
480
+ raise requests.HTTPError(errors, response=response)
481
+ # the CSV is in the response content as a UTF-8 bytestring
482
+ # to use pandas we need to create a file buffer from the response
483
+ fbuf = io.StringIO(response.content.decode('utf-8'))
484
+ return read_nsrdb_psm4(fbuf, map_variables)
485
+
486
+
487
+ def get_nsrdb_psm4_full_disc(latitude, longitude, api_key, email,
488
+ year, time_step=60,
489
+ parameters=PARAMETERS, leap_day=True,
490
+ full_name=PVLIB_PYTHON,
491
+ affiliation=PVLIB_PYTHON, utc=False,
492
+ map_variables=True, url=None, timeout=30):
493
+ """
494
+ Retrieve NSRDB PSM4 timeseries weather data from the PSM4 NSRDB GOES Full
495
+ Disc v4 API.
496
+
497
+ The NSRDB is described in [1]_ and the PSM4 NSRDB GOES Full Disc v4 API is
498
+ described in [2]_.
499
+
500
+ Parameters
501
+ ----------
502
+ latitude : float or int
503
+ in decimal degrees, between -90 and 90, north is positive
504
+ longitude : float or int
505
+ in decimal degrees, between -180 and 180, east is positive
506
+ api_key : str
507
+ NREL Developer Network API key
508
+ email : str
509
+ NREL API uses this to automatically communicate messages back
510
+ to the user only if necessary
511
+ year : int or str
512
+ PSM4 API parameter specifing year (e.g. ``2023``) to download. The
513
+ allowed values update periodically, so consult the NSRDB reference
514
+ below for the current set of options. Called ``names`` in NSRDB API.
515
+ time_step : int, {60, 10, 30}
516
+ time step in minutes, must be 10, 30 or 60. Called ``interval`` in
517
+ NSRDB API.
518
+ parameters : list of str, optional
519
+ meteorological fields to fetch. If not specified, defaults to
520
+ ``pvlib.iotools.psm4.PARAMETERS``. See reference [2]_ for a list of
521
+ available fields. Alternatively, pvlib names may also be used (e.g.
522
+ 'ghi' rather than 'GHI'); see :const:`REQUEST_VARIABLE_MAP`. To
523
+ retrieve all available fields, set ``parameters=[]``.
524
+ leap_day : bool, default : True
525
+ include leap day in the results
526
+ full_name : str, default 'pvlib python'
527
+ optional
528
+ affiliation : str, default 'pvlib python'
529
+ optional
530
+ utc: bool, default : False
531
+ retrieve data with timestamps converted to UTC. False returns
532
+ timestamps in local standard time of the selected location
533
+ map_variables : bool, default True
534
+ When true, renames columns of the Dataframe to pvlib variable names
535
+ where applicable. See variable :const:`VARIABLE_MAP`.
536
+ url : str, optional
537
+ Full API endpoint URL. If not specified, the PSM4 GOES Full Disc v4
538
+ URL is used.
539
+ timeout : int, default 30
540
+ time in seconds to wait for server response before timeout
541
+
542
+ Returns
543
+ -------
544
+ data : pandas.DataFrame
545
+ timeseries data from NREL PSM4
546
+ metadata : dict
547
+ metadata from NREL PSM4 about the record, see
548
+ :func:`pvlib.iotools.read_nsrdb_psm4` for fields
549
+
550
+ Raises
551
+ ------
552
+ requests.HTTPError
553
+ if the request response status is not ok, then the ``'errors'`` field
554
+ from the JSON response or any error message in the content will be
555
+ raised as an exception, for example if the `api_key` was rejected or if
556
+ the coordinates were not found in the NSRDB
557
+
558
+ Notes
559
+ -----
560
+ The required NREL developer key, `api_key`, is available for free by
561
+ registering at the `NREL Developer Network <https://developer.nrel.gov/>`_.
562
+
563
+ .. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
564
+ result in rejected requests.
565
+
566
+ .. warning:: PSM4 is limited to data found in the NSRDB, please consult
567
+ the references below for locations with available data.
568
+
569
+ See Also
570
+ --------
571
+ pvlib.iotools.get_nsrdb_psm4_aggregated,
572
+ pvlib.iotools.get_nsrdb_psm4_tmy, pvlib.iotools.get_nsrdb_psm4_conus,
573
+ pvlib.iotools.read_nsrdb_psm4
574
+
575
+ References
576
+ ----------
577
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
578
+ <https://nsrdb.nrel.gov/>`_
579
+ .. [2] `NSRDB GOES Full Disc V4.0.0
580
+ <https://developer.nrel.gov/docs/solar/nsrdb/nsrdb-GOES-full-disc-v4-0-0-download/>`_
581
+ """
582
+ # The well know text (WKT) representation of geometry notation is strict.
583
+ # A POINT object is a string with longitude first, then the latitude, with
584
+ # four decimals each, and exactly one space between them.
585
+ longitude = ('%9.4f' % longitude).strip()
586
+ latitude = ('%8.4f' % latitude).strip()
587
+ # TODO: make format_WKT(object_type, *args) in tools.py
588
+
589
+ # convert pvlib names in parameters to PSM4 convention
590
+ parameters = [REQUEST_VARIABLE_MAP.get(a, a) for a in parameters]
591
+
592
+ # required query-string parameters for request to PSM4 API
593
+ params = {
594
+ 'api_key': api_key,
595
+ 'full_name': full_name,
596
+ 'email': email,
597
+ 'affiliation': affiliation,
598
+ 'reason': PVLIB_PYTHON,
599
+ 'mailing_list': 'false',
600
+ 'wkt': 'POINT(%s %s)' % (longitude, latitude),
601
+ 'names': year,
602
+ 'attributes': ','.join(parameters),
603
+ 'leap_day': str(leap_day).lower(),
604
+ 'utc': str(utc).lower(),
605
+ 'interval': time_step
606
+ }
607
+ # request CSV download from NREL PSM4
608
+ if url is None:
609
+ url = PSM4_FUL_URL
610
+
611
+ response = requests.get(url, params=params, timeout=timeout)
612
+ if not response.ok:
613
+ # if the API key is rejected, then the response status will be 403
614
+ # Forbidden, and then the error is in the content and there is no JSON
615
+ try:
616
+ errors = response.json()['errors']
617
+ except JSONDecodeError:
618
+ errors = response.content.decode('utf-8')
619
+ raise requests.HTTPError(errors, response=response)
620
+ # the CSV is in the response content as a UTF-8 bytestring
621
+ # to use pandas we need to create a file buffer from the response
622
+ fbuf = io.StringIO(response.content.decode('utf-8'))
623
+ return read_nsrdb_psm4(fbuf, map_variables)
624
+
625
+
626
+ def read_nsrdb_psm4(filename, map_variables=True):
627
+ """
628
+ Read an NSRDB PSM4 weather file (formatted as SAM CSV).
629
+
630
+ The NSRDB is described in [1]_ and the SAM CSV format is described in [2]_.
631
+
632
+ Parameters
633
+ ----------
634
+ filename: str, path-like, or buffer
635
+ Filename or in-memory buffer of a file containing data to read.
636
+ map_variables: bool, default True
637
+ When true, renames columns of the Dataframe to pvlib variable names
638
+ where applicable. See variable :const:`VARIABLE_MAP`.
639
+
640
+ Returns
641
+ -------
642
+ data : pandas.DataFrame
643
+ timeseries data from NREL PSM4
644
+ metadata : dict
645
+ metadata from NREL PSM4 about the record, see notes for fields
646
+
647
+ Notes
648
+ -----
649
+ The return is a tuple with two items. The first item is a dataframe with
650
+ the PSM4 timeseries data.
651
+
652
+ The second item is a dictionary with metadata from NREL PSM4 about the
653
+ record containing the following fields:
654
+
655
+ * Source
656
+ * Location ID
657
+ * City
658
+ * State
659
+ * Country
660
+ * Latitude
661
+ * Longitude
662
+ * Time Zone
663
+ * Elevation
664
+ * Local Time Zone
665
+ * Clearsky DHI Units
666
+ * Clearsky DNI Units
667
+ * Clearsky GHI Units
668
+ * Dew Point Units
669
+ * DHI Units
670
+ * DNI Units
671
+ * GHI Units
672
+ * Solar Zenith Angle Units
673
+ * Temperature Units
674
+ * Pressure Units
675
+ * Relative Humidity Units
676
+ * Precipitable Water Units
677
+ * Wind Direction Units
678
+ * Wind Speed Units
679
+ * Cloud Type -15
680
+ * Cloud Type 0
681
+ * Cloud Type 1
682
+ * Cloud Type 2
683
+ * Cloud Type 3
684
+ * Cloud Type 4
685
+ * Cloud Type 5
686
+ * Cloud Type 6
687
+ * Cloud Type 7
688
+ * Cloud Type 8
689
+ * Cloud Type 9
690
+ * Cloud Type 10
691
+ * Cloud Type 11
692
+ * Cloud Type 12
693
+ * Fill Flag 0
694
+ * Fill Flag 1
695
+ * Fill Flag 2
696
+ * Fill Flag 3
697
+ * Fill Flag 4
698
+ * Fill Flag 5
699
+ * Surface Albedo Units
700
+ * Version
701
+
702
+ Examples
703
+ --------
704
+ >>> # Read a local PSM4 file:
705
+ >>> df, metadata = iotools.read_nsrdb_psm4("data.csv") # doctest: +SKIP
706
+
707
+ >>> # Read a file object or an in-memory buffer:
708
+ >>> with open(filename, 'r') as f: # doctest: +SKIP
709
+ ... df, metadata = iotools.read_nsrdb_psm4(f) # doctest: +SKIP
710
+
711
+ See Also
712
+ --------
713
+ pvlib.iotools.get_nsrdb_psm4_aggregated
714
+ pvlib.iotools.get_nsrdb_psm4_tmy
715
+ pvlib.iotools.get_nsrdb_psm4_conus
716
+ pvlib.iotools.get_nsrdb_psm4_full_disc
717
+ pvlib.iotools.read_psm3
718
+
719
+ References
720
+ ----------
721
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
722
+ <https://nsrdb.nrel.gov/>`_
723
+ .. [2] `Standard Time Series Data File Format
724
+ <https://web.archive.org/web/20170207203107/https://sam.nrel.gov/sites/default/files/content/documents/pdf/wfcsv.pdf>`_
725
+ """
726
+ with tools._file_context_manager(filename) as fbuf:
727
+ # The first 2 lines of the response are headers with metadata
728
+ metadata_fields = fbuf.readline().split(',')
729
+ metadata_values = fbuf.readline().split(',')
730
+ # get the column names so we can set the dtypes
731
+ columns = fbuf.readline().split(',')
732
+ columns[-1] = columns[-1].strip() # strip trailing newline
733
+ # Since the header has so many columns, excel saves blank cols in the
734
+ # data below the header lines.
735
+ columns = [col for col in columns if col != '']
736
+ dtypes = dict.fromkeys(columns, float)
737
+ dtypes.update({'Year': int, 'Month': int, 'Day': int, 'Hour': int,
738
+ 'Minute': int, 'Cloud Type': int, 'Fill Flag': int})
739
+
740
+ data = pd.read_csv(
741
+ fbuf, header=None, names=columns, usecols=columns, dtype=dtypes,
742
+ delimiter=',', lineterminator='\n') # skip carriage returns \r
743
+
744
+ metadata_fields[-1] = metadata_fields[-1].strip() # trailing newline
745
+ metadata_values[-1] = metadata_values[-1].strip() # trailing newline
746
+ metadata = dict(zip(metadata_fields, metadata_values))
747
+ # the response is all strings, so set some metadata types to numbers
748
+ metadata['Local Time Zone'] = int(metadata['Local Time Zone'])
749
+ metadata['Time Zone'] = int(metadata['Time Zone'])
750
+ metadata['Latitude'] = float(metadata['Latitude'])
751
+ metadata['Longitude'] = float(metadata['Longitude'])
752
+ metadata['Elevation'] = int(metadata['Elevation'])
753
+
754
+ # the response 1st 5 columns are a date vector, convert to datetime
755
+ dtidx = pd.to_datetime(data[['Year', 'Month', 'Day', 'Hour', 'Minute']])
756
+ # in USA all timezones are integers
757
+ tz = 'Etc/GMT%+d' % -metadata['Time Zone']
758
+ data.index = pd.DatetimeIndex(dtidx).tz_localize(tz)
759
+
760
+ if map_variables:
761
+ data = data.rename(columns=VARIABLE_MAP)
762
+ metadata['latitude'] = metadata.pop('Latitude')
763
+ metadata['longitude'] = metadata.pop('Longitude')
764
+ metadata['altitude'] = metadata.pop('Elevation')
765
+
766
+ return data, metadata