Skip to content

Commit 99c48a3

Browse files
committed
Add NSRDB PSM4 polar and polar TMY iotools
1 parent 0f7a205 commit 99c48a3

5 files changed

Lines changed: 8933 additions & 0 deletions

File tree

docs/sphinx/source/reference/iotools.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Satellite-derived irradiance and weather data for the Americas.
8080
iotools.get_nsrdb_psm4_tmy
8181
iotools.get_nsrdb_psm4_conus
8282
iotools.get_nsrdb_psm4_full_disc
83+
iotools.get_nsrdb_psm4_polar
8384
iotools.read_nsrdb_psm4
8485

8586

pvlib/iotools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pvlib.iotools.psm4 import get_nsrdb_psm4_tmy # noqa: F401
1313
from pvlib.iotools.psm4 import get_nsrdb_psm4_conus # noqa: F401
1414
from pvlib.iotools.psm4 import get_nsrdb_psm4_full_disc # noqa: F401
15+
from pvlib.iotools.psm4 import get_nsrdb_psm4_polar # noqa: F401
1516
from pvlib.iotools.psm4 import read_nsrdb_psm4 # noqa: F401
1617
from pvlib.iotools.pvgis import get_pvgis_tmy, read_pvgis_tmy # noqa: F401
1718
from pvlib.iotools.pvgis import read_pvgis_hourly # noqa: F401

pvlib/iotools/psm4.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-GOES-tmy-v4-0-0-download/
55
https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-GOES-conus-v4-0-0-download/
66
https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-GOES-full-disc-v4-0-0-download/
7+
https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-polar-v4-0-0-download/
8+
https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-polar-tmy-v4-0-0-download/
79
"""
810

911
import csv
@@ -19,10 +21,14 @@
1921
PSM4_TMY_ENDPOINT = "nsrdb-GOES-tmy-v4-0-0-download.csv"
2022
PSM4_CON_ENDPOINT = "nsrdb-GOES-conus-v4-0-0-download.csv"
2123
PSM4_FUL_ENDPOINT = "nsrdb-GOES-full-disc-v4-0-0-download.csv"
24+
PSM4_POLAR_ENDPOINT = "nsrdb-polar-v4-0-0-download.csv"
25+
PSM4_POLAR_TMY_ENDPOINT = "nsrdb-polar-tmy-v4-0-0-download.csv"
2226
PSM4_AGG_URL = urljoin(NSRDB_API_BASE, PSM4_AGG_ENDPOINT)
2327
PSM4_TMY_URL = urljoin(NSRDB_API_BASE, PSM4_TMY_ENDPOINT)
2428
PSM4_CON_URL = urljoin(NSRDB_API_BASE, PSM4_CON_ENDPOINT)
2529
PSM4_FUL_URL = urljoin(NSRDB_API_BASE, PSM4_FUL_ENDPOINT)
30+
PSM4_POLAR_URL = urljoin(NSRDB_API_BASE, PSM4_POLAR_ENDPOINT)
31+
PSM4_POLAR_TMY_URL = urljoin(NSRDB_API_BASE, PSM4_POLAR_TMY_ENDPOINT)
2632

2733
PARAMETERS = (
2834
'air_temperature', 'dew_point', 'dhi', 'dni', 'ghi', 'surface_albedo',
@@ -624,6 +630,151 @@ def get_nsrdb_psm4_full_disc(latitude, longitude, api_key, email,
624630
return read_nsrdb_psm4(fbuf, map_variables)
625631

626632

633+
def get_nsrdb_psm4_polar(latitude, longitude, api_key, email,
634+
year, *, time_step=60,
635+
parameters=PARAMETERS, leap_day=True,
636+
full_name=PVLIB_PYTHON,
637+
affiliation=PVLIB_PYTHON, utc=False,
638+
map_variables=True, url=None, timeout=30):
639+
"""
640+
Retrieve timeseries weather data from the PSM4 NSRDB Polar API.
641+
642+
The NSRDB is described in [1]_ and the NSRDB PSM4 NSRDB Polar v4 dataset is
643+
described in [2]_, [3]_. The Polar dataset extends NSRDB coverage to high latitude
644+
regions, that are outside the field of view of the geostationary satellites.
645+
646+
The dataset provides data from 2013 onwards over the exclusive economic zone
647+
above 60 degrees north. The data have a spatial resolution of 4 km and a
648+
temporal resolution of 1 hour.
649+
650+
Parameters
651+
----------
652+
latitude : float or int
653+
in decimal degrees, between 60 and 90 degrees north
654+
longitude : float or int
655+
in decimal degrees, between -180 and 180, east is positive
656+
api_key : str
657+
NLR Developer Network API key
658+
email : str
659+
NLR API uses this to automatically communicate messages back
660+
to the user only if necessary
661+
year : int or str
662+
PSM4 API parameter specifing year (e.g. ``2023``) to download. The
663+
allowed values update periodically, so consult the NSRDB reference
664+
below for the current set of options. Called ``names`` in NSRDB API.
665+
time_step : int, default 60
666+
time step in minutes. Called ``interval`` in NSRDB API.
667+
Only 60 is supported for the Polar dataset.
668+
parameters : list of str, optional
669+
meteorological fields to fetch. If not specified, defaults to
670+
``pvlib.iotools.psm4.PARAMETERS``. See reference [2]_ for a list of
671+
available fields. Alternatively, pvlib names may also be used (e.g.
672+
'ghi' rather than 'GHI'); see :const:`REQUEST_VARIABLE_MAP`. To
673+
retrieve all available fields, set ``parameters=[]``.
674+
leap_day : bool, default : True
675+
include leap day in the results
676+
full_name : str, default 'pvlib python'
677+
optional
678+
affiliation : str, default 'pvlib python'
679+
optional
680+
utc: bool, default : False
681+
retrieve data with timestamps converted to UTC. False returns
682+
timestamps in local standard time of the selected location
683+
map_variables : bool, default True
684+
When true, renames columns of the Dataframe to pvlib variable names
685+
where applicable. See variable :const:`VARIABLE_MAP`.
686+
url : str, optional
687+
Full API endpoint URL. If not specified, the PSM4 Polar v4 URL is
688+
used.
689+
timeout : int, default 30
690+
time in seconds to wait for server response before timeout
691+
692+
Returns
693+
-------
694+
data : pandas.DataFrame
695+
timeseries data from NLR PSM4
696+
metadata : dict
697+
metadata from NLR PSM4 about the record, see
698+
:func:`pvlib.iotools.read_nsrdb_psm4` for fields
699+
700+
Raises
701+
------
702+
requests.HTTPError
703+
if the request response status is not ok, then the ``'errors'`` field
704+
from the JSON response or any error message in the content will be
705+
raised as an exception, for example if the `api_key` was rejected or if
706+
the coordinates were not found in the NSRDB
707+
708+
Notes
709+
-----
710+
The required NLR developer key, `api_key`, is available for free by
711+
registering at the `NLR Developer Network <https://developer.nlr.gov/>`_.
712+
713+
.. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
714+
result in rejected requests.
715+
716+
.. warning:: PSM4 is limited to data found in the NSRDB, please consult
717+
the references below for locations with available data.
718+
719+
See Also
720+
--------
721+
pvlib.iotools.get_nsrdb_psm4_aggregated, pvlib.iotools.get_nsrdb_psm4_tmy,
722+
pvlib.iotools.get_nsrdb_psm4_conus, pvlib.iotools.get_nsrdb_psm4_full_disc,
723+
pvlib.iotools.read_nsrdb_psm4
724+
725+
References
726+
----------
727+
.. [1] `NLR National Solar Radiation Database (NSRDB)
728+
<https://developer.nlr.gov/docs/solar/nsrdb/>`_
729+
.. [2] `NLR NSRDB Polar Data
730+
<https://nsrdb.nlr.gov/data-sets/polar-data/>`_
731+
.. [3] `NSRDB Polar V4.0.0
732+
<https://developer.nlr.gov/docs/solar/nsrdb/nsrdb-polar-v4-0-0-download/>`_
733+
"""
734+
# The well know text (WKT) representation of geometry notation is strict.
735+
# A POINT object is a string with longitude first, then the latitude, with
736+
# four decimals each, and exactly one space between them.
737+
longitude = ('%9.4f' % longitude).strip()
738+
latitude = ('%8.4f' % latitude).strip()
739+
# TODO: make format_WKT(object_type, *args) in tools.py
740+
741+
# convert pvlib names in parameters to PSM4 convention
742+
parameters = [REQUEST_VARIABLE_MAP.get(a, a) for a in parameters]
743+
744+
# required query-string parameters for request to PSM4 API
745+
params = {
746+
'api_key': api_key,
747+
'full_name': full_name,
748+
'email': email,
749+
'affiliation': affiliation,
750+
'reason': PVLIB_PYTHON,
751+
'mailing_list': 'false',
752+
'wkt': 'POINT(%s %s)' % (longitude, latitude),
753+
'names': year,
754+
'attributes': ','.join(parameters),
755+
'leap_day': str(leap_day).lower(),
756+
'utc': str(utc).lower(),
757+
'interval': time_step
758+
}
759+
# request CSV download from NLR PSM4
760+
if url is None:
761+
url = PSM4_POLAR_URL
762+
763+
response = requests.get(url, params=params, timeout=timeout)
764+
if not response.ok:
765+
# if the API key is rejected, then the response status will be 403
766+
# Forbidden, and then the error is in the content and there is no JSON
767+
try:
768+
errors = response.json()['errors']
769+
except JSONDecodeError:
770+
errors = response.content.decode('utf-8')
771+
raise requests.HTTPError(errors, response=response)
772+
# the CSV is in the response content as a UTF-8 bytestring
773+
# to use pandas we need to create a file buffer from the response
774+
fbuf = io.StringIO(response.content.decode('utf-8'))
775+
return read_nsrdb_psm4(fbuf, map_variables)
776+
777+
627778
def read_nsrdb_psm4(filename, map_variables=True):
628779
"""
629780
Read an NSRDB PSM4 weather file (formatted as SAM CSV).

0 commit comments

Comments
 (0)