9
9
import pandas as pd
10
10
from json import JSONDecodeError
11
11
12
- URL = "http://developer.nrel.gov/api/solar/nsrdb_psm3_download.csv"
12
+ NSRDB_API_BASE = "https://developer.nrel.gov"
13
+ PSM_URL = NSRDB_API_BASE + "/api/solar/nsrdb_psm3_download.csv"
14
+ TMY_URL = NSRDB_API_BASE + "/api/nsrdb_api/solar/nsrdb_psm3_tmy_download.csv"
13
15
14
16
# 'relative_humidity', 'total_precipitable_water' are not available
15
17
ATTRIBUTES = [
19
21
20
22
21
23
def get_psm3 (latitude , longitude , api_key , email , names = 'tmy' , interval = 60 ,
22
- full_name = PVLIB_PYTHON , affiliation = PVLIB_PYTHON , timeout = 30 ):
24
+ leap_day = False , full_name = PVLIB_PYTHON , affiliation = PVLIB_PYTHON ,
25
+ timeout = 30 ):
23
26
"""
24
- Get PSM3 data
27
+ Retrieve NSRDB [1]_ PSM3 timeseries weather data from the PSM3 API [2]_
28
+ [3]_.
25
29
26
30
Parameters
27
31
----------
@@ -38,7 +42,11 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
38
42
PSM3 API parameter specifing year or TMY variant to download, see notes
39
43
below for options
40
44
interval : int, default 60
41
- interval size in minutes, can only be either 30 or 60
45
+ interval size in minutes, can only be either 30 or 60. Only used for
46
+ single-year requests (i.e., it is ignored for tmy/tgy/tdy requests).
47
+ leap_day : boolean, default False
48
+ include leap day in the results. Only used for single-year requests
49
+ (i.e., it is ignored for tmy/tgy/tdy requests).
42
50
full_name : str, default 'pvlib python'
43
51
optional
44
52
affiliation : str, default 'pvlib python'
@@ -49,7 +57,8 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
49
57
Returns
50
58
-------
51
59
headers : dict
52
- metadata from NREL PSM3 about the record, see notes for fields
60
+ metadata from NREL PSM3 about the record, see
61
+ :func:`pvlib.iotools.parse_psm3` for fields
53
62
data : pandas.DataFrame
54
63
timeseries data from NREL PSM3
55
64
@@ -74,50 +83,25 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
74
83
75
84
['1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005',
76
85
'2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013',
77
- '2014', '2015', '2016', '2017', 'tmy', 'tmy-2016', 'tmy-2017',
78
- 'tdy-2017', 'tgy-2017']
79
-
80
- The return is a tuple with two items. The first item is a header with
81
- metadata from NREL PSM3 about the record containing the following fields:
82
-
83
- * Source
84
- * Location ID
85
- * City
86
- * State
87
- * Country
88
- * Latitude
89
- * Longitude
90
- * Time Zone
91
- * Elevation
92
- * Local Time Zone
93
- * Dew Point Units
94
- * DHI Units
95
- * DNI Units
96
- * GHI Units
97
- * Temperature Units
98
- * Pressure Units
99
- * Wind Direction Units
100
- * Wind Speed
101
- * Surface Albedo Units
102
- * Version
103
-
104
- The second item is a dataframe with the timeseries data downloaded.
86
+ '2014', '2015', '2016', '2017', '2018', 'tmy', 'tmy-2016', 'tmy-2017',
87
+ 'tdy-2017', 'tgy-2017', 'tmy-2018', 'tdy-2018', 'tgy-2018']
105
88
106
89
.. warning:: PSM3 is limited to data found in the NSRDB, please consult the
107
90
references below for locations with available data
108
91
109
92
See Also
110
93
--------
111
- pvlib.iotools.read_tmy2 , pvlib.iotools.read_tmy3
94
+ pvlib.iotools.read_psm3 , pvlib.iotools.parse_psm3
112
95
113
96
References
114
97
----------
115
98
116
- .. [1] `NREL Developer Network - Physical Solar Model (PSM) v3
117
- <https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/>`_
118
- .. [2] `NREL National Solar Radiation Database (NSRDB)
99
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
119
100
<https://nsrdb.nrel.gov/>`_
120
-
101
+ .. [2] `NREL Developer Network - Physical Solar Model (PSM) v3
102
+ <https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/>`_
103
+ .. [3] `NREL Developer Network - Physical Solar Model (PSM) v3 TMY
104
+ <https://developer.nrel.gov/docs/solar/nsrdb/psm3_tmy_data_download/>`_
121
105
"""
122
106
# The well know text (WKT) representation of geometry notation is strict.
123
107
# A POINT object is a string with longitude first, then the latitude, with
@@ -137,11 +121,15 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
137
121
'wkt' : 'POINT(%s %s)' % (longitude , latitude ),
138
122
'names' : names ,
139
123
'attributes' : ',' .join (ATTRIBUTES ),
140
- 'leap_day' : 'false' ,
124
+ 'leap_day' : str ( leap_day ). lower () ,
141
125
'utc' : 'false' ,
142
126
'interval' : interval
143
127
}
144
128
# request CSV download from NREL PSM3
129
+ if any (prefix in names for prefix in ('tmy' , 'tgy' , 'tdy' )):
130
+ URL = TMY_URL
131
+ else :
132
+ URL = PSM_URL
145
133
response = requests .get (URL , params = params , timeout = timeout )
146
134
if not response .ok :
147
135
# if the API key is rejected, then the response status will be 403
@@ -154,7 +142,97 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
154
142
# the CSV is in the response content as a UTF-8 bytestring
155
143
# to use pandas we need to create a file buffer from the response
156
144
fbuf = io .StringIO (response .content .decode ('utf-8' ))
157
- # The first 2 lines of the response are headers with metadat
145
+ return parse_psm3 (fbuf )
146
+
147
+
148
+ def parse_psm3 (fbuf ):
149
+ """
150
+ Parse an NSRDB [1]_ PSM3 weather file (formatted as SAM CSV [2]_).
151
+
152
+ Parameters
153
+ ----------
154
+ fbuf: file-like object
155
+ File-like object containing data to read.
156
+
157
+ Returns
158
+ -------
159
+ headers : dict
160
+ metadata from NREL PSM3 about the record, see notes for fields
161
+ data : pandas.DataFrame
162
+ timeseries data from NREL PSM3
163
+
164
+ Notes
165
+ -----
166
+ The return is a tuple with two items. The first item is a header with
167
+ metadata from NREL PSM3 about the record containing the following fields:
168
+
169
+ * Source
170
+ * Location ID
171
+ * City
172
+ * State
173
+ * Country
174
+ * Latitude
175
+ * Longitude
176
+ * Time Zone
177
+ * Elevation
178
+ * Local Time Zone
179
+ * Clearsky DHI Units
180
+ * Clearsky DNI Units
181
+ * Clearsky GHI Units
182
+ * Dew Point Units
183
+ * DHI Units
184
+ * DNI Units
185
+ * GHI Units
186
+ * Solar Zenith Angle Units
187
+ * Temperature Units
188
+ * Pressure Units
189
+ * Relative Humidity Units
190
+ * Precipitable Water Units
191
+ * Wind Direction Units
192
+ * Wind Speed
193
+ * Cloud Type -15
194
+ * Cloud Type 0
195
+ * Cloud Type 1
196
+ * Cloud Type 2
197
+ * Cloud Type 3
198
+ * Cloud Type 4
199
+ * Cloud Type 5
200
+ * Cloud Type 6
201
+ * Cloud Type 7
202
+ * Cloud Type 8
203
+ * Cloud Type 9
204
+ * Cloud Type 10
205
+ * Cloud Type 11
206
+ * Cloud Type 12
207
+ * Fill Flag 0
208
+ * Fill Flag 1
209
+ * Fill Flag 2
210
+ * Fill Flag 3
211
+ * Fill Flag 4
212
+ * Fill Flag 5
213
+ * Surface Albedo Units
214
+ * Version
215
+
216
+ The second item is a dataframe with the PSM3 timeseries data.
217
+
218
+ Examples
219
+ --------
220
+ >>> # Read a local PSM3 file:
221
+ >>> with open(filename, 'r') as f: # doctest: +SKIP
222
+ ... metadata, df = iotools.parse_psm3(f) # doctest: +SKIP
223
+
224
+ See Also
225
+ --------
226
+ pvlib.iotools.read_psm3, pvlib.iotools.get_psm3
227
+
228
+ References
229
+ ----------
230
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
231
+ <https://nsrdb.nrel.gov/>`_
232
+ .. [2] `Standard Time Series Data File Format
233
+ <https://rredc.nrel.gov/solar/old_data/nsrdb/2005-2012/wfcsv.pdf>`_
234
+ """
235
+ # The first 2 lines of the response are headers with metadata
158
236
header_fields = fbuf .readline ().split (',' )
159
237
header_fields [- 1 ] = header_fields [- 1 ].strip () # strip trailing newline
160
238
header_values = fbuf .readline ().split (',' )
@@ -169,15 +247,54 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
169
247
# get the column names so we can set the dtypes
170
248
columns = fbuf .readline ().split (',' )
171
249
columns [- 1 ] = columns [- 1 ].strip () # strip trailing newline
250
+ # Since the header has so many columns, excel saves blank cols in the
251
+ # data below the header lines.
252
+ columns = [col for col in columns if col != '' ]
172
253
dtypes = dict .fromkeys (columns , float ) # all floats except datevec
173
254
dtypes .update (Year = int , Month = int , Day = int , Hour = int , Minute = int )
255
+ dtypes ['Cloud Type' ] = int
256
+ dtypes ['Fill Flag' ] = int
174
257
data = pd .read_csv (
175
- fbuf , header = None , names = columns , dtype = dtypes ,
258
+ fbuf , header = None , names = columns , usecols = columns , dtype = dtypes ,
176
259
delimiter = ',' , lineterminator = '\n ' ) # skip carriage returns \r
177
260
# the response 1st 5 columns are a date vector, convert to datetime
178
261
dtidx = pd .to_datetime (
179
262
data [['Year' , 'Month' , 'Day' , 'Hour' , 'Minute' ]])
180
- # in USA all timezones are intergers
263
+ # in USA all timezones are integers
181
264
tz = 'Etc/GMT%+d' % - header ['Time Zone' ]
182
265
data .index = pd .DatetimeIndex (dtidx ).tz_localize (tz )
266
+
183
267
return header , data
268
+
269
+
270
+ def read_psm3 (filename ):
271
+ """
272
+ Read an NSRDB [1]_ PSM3 weather file (formatted as SAM CSV [2]_).
273
+
274
+ Parameters
275
+ ----------
276
+ filename: str
277
+ Filename of a file containing data to read.
278
+
279
+ Returns
280
+ -------
281
+ headers : dict
282
+ metadata from NREL PSM3 about the record, see
283
+ :func:`pvlib.iotools.parse_psm3` for fields
284
+ data : pandas.DataFrame
285
+ timeseries data from NREL PSM3
286
+
287
+ See Also
288
+ --------
289
+ pvlib.iotools.parse_psm3, pvlib.iotools.get_psm3
290
+
291
+ References
292
+ ----------
293
+ .. [1] `NREL National Solar Radiation Database (NSRDB)
294
+ <https://nsrdb.nrel.gov/>`_
295
+ .. [2] `Standard Time Series Data File Format
296
+ <https://rredc.nrel.gov/solar/old_data/nsrdb/2005-2012/wfcsv.pdf>`_
297
+ """
298
+ with open (filename , 'r' ) as fbuf :
299
+ content = parse_psm3 (fbuf )
300
+ return content
0 commit comments