Skip to content

ENH: Create psm3.py #694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 3, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ relevant to solar energy modeling.
iotools.get_ecmwf_macc
iotools.read_crn
iotools.read_solrad
iotools.get_psm3

A :py:class:`~pvlib.location.Location` object may be created from metadata
in some files.
Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/source/whatsnew/v0.6.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Enhancements
* Add US CRN data reader to :ref:`iotools`.
* Add SOLRAD data reader to :ref:`iotools`.
* Add EPW data reader to :ref:`iotools`. (:issue:`591`)
* Add PSM3 reader to :ref:`iotools`. (:issue:`592`)

Bug fixes
~~~~~~~~~
Expand Down
8,761 changes: 8,761 additions & 0 deletions pvlib/data/test_psm3.csv

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pvlib/iotools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from pvlib.iotools.ecmwf_macc import get_ecmwf_macc # noqa: F401
from pvlib.iotools.crn import read_crn # noqa: F401
from pvlib.iotools.solrad import read_solrad # noqa: F401
from pvlib.iotools.psm3 import get_psm3 # noqa: F401
133 changes: 133 additions & 0 deletions pvlib/iotools/psm3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Get PSM3 TMY
see https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/
"""

import io
import csv
import datetime
import requests
import pandas as pd
import pytz

URL = "http://developer.nrel.gov/api/solar/nsrdb_psm3_download.csv"

# 'relative_humidity', 'total_precipitable_water' are note available
ATTRIBUTES = [
'air_temperature', 'dew_point', 'dhi', 'dni', 'ghi', 'surface_albedo',
'surface_pressure', 'wind_direction', 'wind_speed']


def get_psm3(latitude, longitude, tmy='tmy', interval=60):
"""
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
tmy : str
PSM3 parameter "name", see notes below for options, default ``'tmy'``
interval : integer
interval size in minutes, can be only either 30 or 60, default 60

Returns
-------
headers : dict
meta data from NREL PSM-3 about the record, see notes for fields
data : pandas.DataFrame
timeseries data from NREL PSM3

Raises
------
requests.HTTPError

Notes
-----
The ``tmy`` parameter 'names' 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 meta
data 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

See Also
--------
pvlib.iotools.read_tmy2, pvlib.iotools.read_tmy3

References
----------
[1] `NREL Developer Network - Physical Solar Model (PSM) v3
<https://developer.nrel.gov/docs/solar/nsrdb/psm3_data_download/>`_
[2] `NREL National Solar Radiation Database (NSRDB)
<https://nsrdb.nrel.gov/>`_
"""
longitude = ('%9.4f' % longitude).strip()
latitude = ('%8.4f' % latitude).strip()
params = {
'api_key': 'DEMO_KEY',
'full_name': 'Sample User',
'email': 'sample@email.com',
'affiliation': 'Test Organization',
'reason': 'Example',
'mailing_list': 'true',
'wkt': 'POINT(%s %s)' % (longitude, latitude),
'names': tmy,
'attributes': ','.join(ATTRIBUTES),
'leap_day': 'false',
'utc': 'false',
'interval': interval
}

s = requests.get(URL, params=params)
if s.ok:
f = io.StringIO(s.content.decode('utf-8'))
x = csv.reader(f, delimiter=',', lineterminator='\n')
y = list(x)
z = dict(zip(y[0], y[1]))
# in USA all timezones are integers
z['Local Time Zone'] = int(z['Local Time Zone'])
z['Time Zone'] = int(z['Time Zone'])
z['Latitude'] = float(z['Latitude'])
z['Longitude'] = float(z['Longitude'])
z['Elevation'] = int(z['Elevation'])
tz = pytz.timezone('Etc/GMT%+d' % -z['Time Zone'])
w = pd.DataFrame(y[3:], columns=y[2], dtype=float)
w.Year = w.Year.apply(lambda yr: int(yr))
w.Month = w.Month.apply(lambda mt: int(mt))
w.Day = w.Day.apply(lambda dy: int(dy))
w.Hour = w.Hour.apply(lambda hr: int(hr))
w.Minute = w.Minute.apply(lambda mn: int(mn))
w.index = w.apply(
lambda dt: tz.localize(datetime.datetime(
int(dt.Year), int(dt.Month), int(dt.Day),
int(dt.Hour), int(dt.Minute))), axis=1)
return z, w
raise requests.HTTPError(s.json()['errors'])
50 changes: 50 additions & 0 deletions pvlib/test/test_psm3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
test iotools for PSM3
"""

import os
from pvlib.iotools import psm3
import numpy as np
import pandas as pd
import pytest
from requests import HTTPError

BASEDIR = os.path.abspath(os.path.dirname(__file__))
PROJDIR = os.path.dirname(BASEDIR)
DATADIR = os.path.join(PROJDIR, 'data')
TEST_DATA = os.path.join(DATADIR, 'test_psm3.csv')
LATITUDE, LONGITUDE = 40.5137, -108.5449
HEADER_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']


def test_psm3():
"""test get_psm3"""
header, data = psm3.get_psm3(LATITUDE, LONGITUDE)
expected = pd.read_csv(TEST_DATA)
# check datevec columns
assert np.allclose(data.Year, expected.Year)
assert np.allclose(data.Month, expected.Month)
assert np.allclose(data.Day, expected.Day)
assert np.allclose(data.Hour, expected.Hour)
assert np.allclose(data.Minute, expected.Minute)
# check data columns
assert np.allclose(data.GHI, expected.GHI)
assert np.allclose(data.DNI, expected.DNI)
assert np.allclose(data.DHI, expected.DHI)
assert np.allclose(data.Temperature, expected.Temperature)
assert np.allclose(data.Pressure, expected.Pressure)
assert np.allclose(data['Dew Point'], expected['Dew Point'])
assert np.allclose(data['Surface Albedo'], expected['Surface Albedo'])
assert np.allclose(data['Wind Speed'], expected['Wind Speed'])
assert np.allclose(data['Wind Direction'], expected['Wind Direction'])
# check header
for hf in HEADER_FIELDS:
assert hf in header
# check error
with pytest.raises(HTTPError):
psm3.get_psm3(LATITUDE, LONGITUDE, 'bad')