"""
Get PSM3 TMY
see https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/
"""
import io
import requests
import pandas as pd
# Python-2 compatible JSONDecodeError
try:
from json import JSONDecodeError
except ImportError:
JSONDecodeError = ValueError
URL = "http://developer.nrel.gov/api/solar/nsrdb_psm3_download.csv"
# 'relative_humidity', 'total_precipitable_water' are not available
ATTRIBUTES = [
'air_temperature', 'dew_point', 'dhi', 'dni', 'ghi', 'surface_albedo',
'surface_pressure', 'wind_direction', 'wind_speed']
PVLIB_PYTHON = 'pvlib python'
[docs]def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
full_name=PVLIB_PYTHON, affiliation=PVLIB_PYTHON):
"""
Get PSM3 data
Parameters
----------
latitude : float or int
in decimal degrees, between -90 and 90, north is positive
longitude : float or int
in decimal degrees, between -180 and 180, east is positive
api_key : str
NREL Developer Network API key
email : str
NREL API uses this to automatically communicate messages back
to the user only if necessary
names : str, default 'tmy'
PSM3 API parameter specifing year or TMY variant to download, see notes
below for options
interval : int, default 60
interval size in minutes, can only be either 30 or 60
full_name : str, default 'pvlib python'
optional
affiliation : str, default 'pvlib python'
optional
Returns
-------
headers : dict
metadata from NREL PSM3 about the record, see notes for fields
data : pandas.DataFrame
timeseries data from NREL PSM3
Raises
------
requests.HTTPError
if the request response status is not ok, then the ``'errors'`` field
from the JSON response or any error message in the content will be
raised as an exception, for example if the `api_key` was rejected or if
the coordinates were not found in the NSRDB
Notes
-----
The required NREL developer key, `api_key`, is available for free by
registering at the `NREL Developer Network <https://developer.nrel.gov/>`_.
.. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
result in rejected requests.
The PSM3 API `names` parameter must be a single value from the following
list::
['1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005',
'2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013',
'2014', '2015', '2016', '2017', 'tmy', 'tmy-2016', 'tmy-2017',
'tdy-2017', 'tgy-2017']
The return is a tuple with two items. The first item is a header with
metadata from NREL PSM3 about the record containing the following fields:
* Source
* Location ID
* City
* State
* Country
* Latitude
* Longitude
* Time Zone
* Elevation
* Local Time Zone
* Dew Point Units
* DHI Units
* DNI Units
* GHI Units
* Temperature Units
* Pressure Units
* Wind Direction Units
* Wind Speed
* Surface Albedo Units
* Version
The second item is a dataframe with the timeseries data downloaded.
.. warning:: PSM3 is limited to data found in the NSRDB, please consult the
references below for locations with available data
See Also
--------
pvlib.iotools.read_tmy2, pvlib.iotools.read_tmy3
References
----------
* `NREL Developer Network - Physical Solar Model (PSM) v3
<https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/>`_
* `NREL National Solar Radiation Database (NSRDB)
<https://nsrdb.nrel.gov/>`_
"""
# The well know text (WKT) representation of geometry notation is strict.
# A POINT object is a string with longitude first, then the latitude, with
# four decimals each, and exactly one space between them.
longitude = ('%9.4f' % longitude).strip()
latitude = ('%8.4f' % latitude).strip()
# TODO: make format_WKT(object_type, *args) in tools.py
# required query-string parameters for request to PSM3 API
params = {
'api_key': api_key,
'full_name': full_name,
'email': email,
'affiliation': affiliation,
'reason': PVLIB_PYTHON,
'mailing_list': 'false',
'wkt': 'POINT(%s %s)' % (longitude, latitude),
'names': names,
'attributes': ','.join(ATTRIBUTES),
'leap_day': 'false',
'utc': 'false',
'interval': interval
}
# request CSV download from NREL PSM3
response = requests.get(URL, params=params)
if not response.ok:
# if the API key is rejected, then the response status will be 403
# Forbidden, and then the error is in the content and there is no JSON
try:
errors = response.json()['errors']
except JSONDecodeError:
errors = response.content.decode('utf-8')
raise requests.HTTPError(errors, response=response)
# the CSV is in the response content as a UTF-8 bytestring
# to use pandas we need to create a file buffer from the response
fbuf = io.StringIO(response.content.decode('utf-8'))
# The first 2 lines of the response are headers with metadat
header_fields = fbuf.readline().split(',')
header_fields[-1] = header_fields[-1].strip() # strip trailing newline
header_values = fbuf.readline().split(',')
header_values[-1] = header_values[-1].strip() # strip trailing newline
header = dict(zip(header_fields, header_values))
# the response is all strings, so set some header types to numbers
header['Local Time Zone'] = int(header['Local Time Zone'])
header['Time Zone'] = int(header['Time Zone'])
header['Latitude'] = float(header['Latitude'])
header['Longitude'] = float(header['Longitude'])
header['Elevation'] = int(header['Elevation'])
# get the column names so we can set the dtypes
columns = fbuf.readline().split(',')
columns[-1] = columns[-1].strip() # strip trailing newline
dtypes = dict.fromkeys(columns, float) # all floats except datevec
dtypes.update(Year=int, Month=int, Day=int, Hour=int, Minute=int)
data = pd.read_csv(
fbuf, header=None, names=columns, dtype=dtypes,
delimiter=',', lineterminator='\n') # skip carriage returns \r
# the response 1st 5 columns are a date vector, convert to datetime
dtidx = pd.to_datetime(
data[['Year', 'Month', 'Day', 'Hour', 'Minute']])
# in USA all timezones are intergers
tz = 'Etc/GMT%+d' % -header['Time Zone']
data.index = pd.DatetimeIndex(dtidx).tz_localize(tz)
return header, data