pvlib 0.9.4a1__py3-none-any.whl → 0.10.0__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.
Files changed (86) hide show
  1. pvlib/__init__.py +3 -2
  2. pvlib/atmosphere.py +23 -173
  3. pvlib/bifacial/infinite_sheds.py +88 -277
  4. pvlib/bifacial/utils.py +270 -28
  5. pvlib/data/adr-library-cec-inverters-2019-03-05.csv +5009 -0
  6. pvlib/data/precise_iv_curves1.json +10251 -0
  7. pvlib/data/precise_iv_curves2.json +10251 -0
  8. pvlib/data/precise_iv_curves_parameter_sets1.csv +33 -0
  9. pvlib/data/precise_iv_curves_parameter_sets2.csv +33 -0
  10. pvlib/data/test_psm3_2017.csv +17521 -17521
  11. pvlib/data/test_psm3_2019_5min.csv +288 -288
  12. pvlib/data/test_read_psm3.csv +17522 -17522
  13. pvlib/data/test_read_pvgis_horizon.csv +49 -0
  14. pvlib/data/variables_style_rules.csv +3 -0
  15. pvlib/iam.py +207 -51
  16. pvlib/inverter.py +6 -1
  17. pvlib/iotools/__init__.py +7 -2
  18. pvlib/iotools/acis.py +516 -0
  19. pvlib/iotools/midc.py +4 -4
  20. pvlib/iotools/psm3.py +59 -42
  21. pvlib/iotools/pvgis.py +84 -28
  22. pvlib/iotools/sodapro.py +8 -6
  23. pvlib/iotools/srml.py +121 -18
  24. pvlib/iotools/surfrad.py +2 -2
  25. pvlib/iotools/tmy.py +146 -102
  26. pvlib/irradiance.py +270 -15
  27. pvlib/ivtools/sde.py +14 -20
  28. pvlib/ivtools/sdm.py +31 -20
  29. pvlib/ivtools/utils.py +127 -6
  30. pvlib/location.py +3 -2
  31. pvlib/modelchain.py +67 -70
  32. pvlib/pvarray.py +225 -0
  33. pvlib/pvsystem.py +169 -539
  34. pvlib/shading.py +43 -2
  35. pvlib/singlediode.py +216 -66
  36. pvlib/snow.py +36 -15
  37. pvlib/soiling.py +3 -3
  38. pvlib/spa.py +327 -368
  39. pvlib/spectrum/__init__.py +8 -2
  40. pvlib/spectrum/mismatch.py +335 -0
  41. pvlib/temperature.py +124 -13
  42. pvlib/tests/bifacial/test_infinite_sheds.py +44 -106
  43. pvlib/tests/bifacial/test_utils.py +102 -5
  44. pvlib/tests/conftest.py +0 -31
  45. pvlib/tests/iotools/test_acis.py +213 -0
  46. pvlib/tests/iotools/test_midc.py +6 -6
  47. pvlib/tests/iotools/test_psm3.py +7 -5
  48. pvlib/tests/iotools/test_pvgis.py +21 -14
  49. pvlib/tests/iotools/test_sodapro.py +1 -1
  50. pvlib/tests/iotools/test_srml.py +71 -6
  51. pvlib/tests/iotools/test_tmy.py +43 -8
  52. pvlib/tests/ivtools/test_sde.py +19 -17
  53. pvlib/tests/ivtools/test_sdm.py +9 -4
  54. pvlib/tests/ivtools/test_utils.py +96 -1
  55. pvlib/tests/test_atmosphere.py +8 -64
  56. pvlib/tests/test_clearsky.py +0 -1
  57. pvlib/tests/test_iam.py +74 -1
  58. pvlib/tests/test_irradiance.py +56 -2
  59. pvlib/tests/test_location.py +1 -1
  60. pvlib/tests/test_modelchain.py +33 -76
  61. pvlib/tests/test_pvarray.py +46 -0
  62. pvlib/tests/test_pvsystem.py +366 -201
  63. pvlib/tests/test_shading.py +35 -0
  64. pvlib/tests/test_singlediode.py +306 -29
  65. pvlib/tests/test_snow.py +84 -1
  66. pvlib/tests/test_soiling.py +8 -7
  67. pvlib/tests/test_solarposition.py +7 -7
  68. pvlib/tests/test_spa.py +6 -7
  69. pvlib/tests/test_spectrum.py +145 -1
  70. pvlib/tests/test_temperature.py +29 -11
  71. pvlib/tests/test_tools.py +41 -0
  72. pvlib/tests/test_tracking.py +0 -149
  73. pvlib/tools.py +49 -25
  74. pvlib/tracking.py +1 -269
  75. pvlib-0.10.0.dist-info/AUTHORS.md +35 -0
  76. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/LICENSE +5 -2
  77. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/METADATA +3 -13
  78. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/RECORD +80 -75
  79. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/WHEEL +1 -1
  80. pvlib/data/adr-library-2013-10-01.csv +0 -1762
  81. pvlib/forecast.py +0 -1211
  82. pvlib/iotools/ecmwf_macc.py +0 -312
  83. pvlib/tests/iotools/test_ecmwf_macc.py +0 -162
  84. pvlib/tests/test_forecast.py +0 -228
  85. pvlib-0.9.4a1.dist-info/AUTHORS.md +0 -32
  86. {pvlib-0.9.4a1.dist-info → pvlib-0.10.0.dist-info}/top_level.txt +0 -0
pvlib/iotools/psm3.py CHANGED
@@ -11,17 +11,16 @@ import warnings
11
11
  from pvlib._deprecation import pvlibDeprecationWarning
12
12
 
13
13
  NSRDB_API_BASE = "https://developer.nrel.gov"
14
- PSM_URL = NSRDB_API_BASE + "/api/nsrdb/v2/solar/psm3-download.csv"
14
+ PSM_URL = NSRDB_API_BASE + "/api/nsrdb/v2/solar/psm3-2-2-download.csv"
15
15
  TMY_URL = NSRDB_API_BASE + "/api/nsrdb/v2/solar/psm3-tmy-download.csv"
16
16
  PSM5MIN_URL = NSRDB_API_BASE + "/api/nsrdb/v2/solar/psm3-5min-download.csv"
17
17
 
18
- # 'relative_humidity', 'total_precipitable_water' are not available
19
18
  ATTRIBUTES = (
20
19
  'air_temperature', 'dew_point', 'dhi', 'dni', 'ghi', 'surface_albedo',
21
20
  'surface_pressure', 'wind_direction', 'wind_speed')
22
21
  PVLIB_PYTHON = 'pvlib python'
23
22
 
24
- # Dictionary mapping PSM3 names to pvlib names
23
+ # Dictionary mapping PSM3 response names to pvlib names
25
24
  VARIABLE_MAP = {
26
25
  'GHI': 'ghi',
27
26
  'DHI': 'dhi',
@@ -31,19 +30,41 @@ VARIABLE_MAP = {
31
30
  'Clearsky DNI': 'dni_clear',
32
31
  'Solar Zenith Angle': 'solar_zenith',
33
32
  'Temperature': 'temp_air',
34
- 'Relative Humidity': 'relative_humidity',
35
33
  'Dew point': 'temp_dew',
34
+ 'Relative Humidity': 'relative_humidity',
36
35
  'Pressure': 'pressure',
37
- 'Wind Direction': 'wind_direction',
38
36
  'Wind Speed': 'wind_speed',
37
+ 'Wind Direction': 'wind_direction',
39
38
  'Surface Albedo': 'albedo',
40
39
  'Precipitable Water': 'precipitable_water',
41
40
  }
42
41
 
42
+ # Dictionary mapping pvlib names to PSM3 request names
43
+ # Note, PSM3 uses different names for the same variables in the
44
+ # response and the request
45
+ REQUEST_VARIABLE_MAP = {
46
+ 'ghi': 'ghi',
47
+ 'dhi': 'dhi',
48
+ 'dni': 'dni',
49
+ 'ghi_clear': 'clearsky_dhi',
50
+ 'dhi_clear': 'clearsky_dhi',
51
+ 'dni_clear': 'clearsky_dni',
52
+ 'zenith': 'solar_zenith_angle',
53
+ 'temp_air': 'air_temperature',
54
+ 'temp_dew': 'dew_point',
55
+ 'relative_humidity': 'relative_humidity',
56
+ 'pressure': 'surface_pressure',
57
+ 'wind_speed': 'wind_speed',
58
+ 'wind_direction': 'wind_direction',
59
+ 'albedo': 'surface_albedo',
60
+ 'precipitable_water': 'total_precipitable_water',
61
+ }
62
+
43
63
 
44
64
  def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
45
65
  attributes=ATTRIBUTES, leap_day=None, full_name=PVLIB_PYTHON,
46
- affiliation=PVLIB_PYTHON, map_variables=None, timeout=30):
66
+ affiliation=PVLIB_PYTHON, map_variables=None, url=None,
67
+ timeout=30):
47
68
  """
48
69
  Retrieve NSRDB PSM3 timeseries weather data from the PSM3 API. The NSRDB
49
70
  is described in [1]_ and the PSM3 API is described in [2]_, [3]_, and [4]_.
@@ -53,6 +74,12 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
53
74
  and the second element is a dictionary containing metadata. Previous
54
75
  versions of this function had the return values switched.
55
76
 
77
+ .. versionchanged:: 0.10.0
78
+ The default endpoint for hourly single-year datasets is now v3.2.2.
79
+ The previous datasets can still be accessed (for now) by setting
80
+ the ``url`` parameter to the original API endpoint
81
+ (``"https://developer.nrel.gov/api/nsrdb/v2/solar/psm3-download.csv"``).
82
+
56
83
  Parameters
57
84
  ----------
58
85
  latitude : float or int
@@ -65,16 +92,19 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
65
92
  NREL API uses this to automatically communicate messages back
66
93
  to the user only if necessary
67
94
  names : str, default 'tmy'
68
- PSM3 API parameter specifing year or TMY variant to download, see notes
69
- below for options
95
+ PSM3 API parameter specifing year (e.g. ``2020``) or TMY variant
96
+ to download (e.g. ``'tmy'`` or ``'tgy-2019'``). The allowed values
97
+ update periodically, so consult the NSRDB references below for the
98
+ current set of options.
70
99
  interval : int, {60, 5, 15, 30}
71
- interval size in minutes, must be 5, 15, 30 or 60. Only used for
72
- single-year requests (i.e., it is ignored for tmy/tgy/tdy requests).
100
+ interval size in minutes, must be 5, 15, 30 or 60. Must be 60 for
101
+ typical year requests (i.e., tmy/tgy/tdy).
73
102
  attributes : list of str, optional
74
103
  meteorological fields to fetch. If not specified, defaults to
75
104
  ``pvlib.iotools.psm3.ATTRIBUTES``. See references [2]_, [3]_, and [4]_
76
105
  for lists of available fields. Alternatively, pvlib names may also be
77
- used (e.g. 'ghi' rather than 'GHI'); see :const:`VARIABLE_MAP`.
106
+ used (e.g. 'ghi' rather than 'GHI'); see :const:`REQUEST_VARIABLE_MAP`.
107
+ To retrieve all available fields, set ``attributes=[]``.
78
108
  leap_day : boolean, default False
79
109
  include leap day in the results. Only used for single-year requests
80
110
  (i.e., it is ignored for tmy/tgy/tdy requests).
@@ -82,9 +112,12 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
82
112
  optional
83
113
  affiliation : str, default 'pvlib python'
84
114
  optional
85
- map_variables: boolean, optional
115
+ map_variables : boolean, optional
86
116
  When true, renames columns of the Dataframe to pvlib variable names
87
117
  where applicable. See variable :const:`VARIABLE_MAP`.
118
+ url : str, optional
119
+ API endpoint URL. If not specified, the endpoint is determined from
120
+ the ``names`` and ``interval`` parameters.
88
121
  timeout : int, default 30
89
122
  time in seconds to wait for server response before timeout
90
123
 
@@ -112,21 +145,6 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
112
145
  .. warning:: The "DEMO_KEY" `api_key` is severely rate limited and may
113
146
  result in rejected requests.
114
147
 
115
- The PSM3 API `names` parameter must be a single value from one of these
116
- lists:
117
-
118
- +-----------+-------------------------------------------------------------+
119
- | Category | Allowed values |
120
- +===========+=============================================================+
121
- | Year | 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, |
122
- | | 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, |
123
- | | 2018, 2019, 2020 |
124
- +-----------+-------------------------------------------------------------+
125
- | TMY | tmy, tmy-2016, tmy-2017, tdy-2017, tgy-2017, |
126
- | | tmy-2018, tdy-2018, tgy-2018, tmy-2019, tdy-2019, tgy-2019 |
127
- | | tmy-2020, tdy-2020, tgy-2020 |
128
- +-----------+-------------------------------------------------------------+
129
-
130
148
  .. warning:: PSM3 is limited to data found in the NSRDB, please consult the
131
149
  references below for locations with available data. Additionally,
132
150
  querying data with < 30-minute resolution uses a different API endpoint
@@ -141,8 +159,8 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
141
159
 
142
160
  .. [1] `NREL National Solar Radiation Database (NSRDB)
143
161
  <https://nsrdb.nrel.gov/>`_
144
- .. [2] `Physical Solar Model (PSM) v3
145
- <https://developer.nrel.gov/docs/solar/nsrdb/psm3-download/>`_
162
+ .. [2] `Physical Solar Model (PSM) v3.2.2
163
+ <https://developer.nrel.gov/docs/solar/nsrdb/psm3-2-2-download/>`_
146
164
  .. [3] `Physical Solar Model (PSM) v3 TMY
147
165
  <https://developer.nrel.gov/docs/solar/nsrdb/psm3-tmy-download/>`_
148
166
  .. [4] `Physical Solar Model (PSM) v3 - Five Minute Temporal Resolution
@@ -158,12 +176,8 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
158
176
  # convert to string to accomodate integer years being passed in
159
177
  names = str(names)
160
178
 
161
- # convert pvlib names in attributes to psm3 convention (reverse mapping)
162
- # unlike psm3 columns, attributes are lower case and with underscores
163
- amap = {value: key.lower().replace(' ', '_') for (key, value) in
164
- VARIABLE_MAP.items()}
165
- attributes = [amap.get(a, a) for a in attributes]
166
- attributes = list(set(attributes)) # remove duplicate values
179
+ # convert pvlib names in attributes to psm3 convention
180
+ attributes = [REQUEST_VARIABLE_MAP.get(a, a) for a in attributes]
167
181
 
168
182
  if (leap_day is None) and (not names.startswith('t')):
169
183
  warnings.warn(
@@ -189,13 +203,16 @@ def get_psm3(latitude, longitude, api_key, email, names='tmy', interval=60,
189
203
  'interval': interval
190
204
  }
191
205
  # request CSV download from NREL PSM3
192
- if any(prefix in names for prefix in ('tmy', 'tgy', 'tdy')):
193
- URL = TMY_URL
194
- elif interval in (5, 15):
195
- URL = PSM5MIN_URL
196
- else:
197
- URL = PSM_URL
198
- response = requests.get(URL, params=params, timeout=timeout)
206
+ if url is None:
207
+ # determine the endpoint that suits the user inputs
208
+ if any(prefix in names for prefix in ('tmy', 'tgy', 'tdy')):
209
+ url = TMY_URL
210
+ elif interval in (5, 15):
211
+ url = PSM5MIN_URL
212
+ else:
213
+ url = PSM_URL
214
+
215
+ response = requests.get(url, params=params, timeout=timeout)
199
216
  if not response.ok:
200
217
  # if the API key is rejected, then the response status will be 403
201
218
  # Forbidden, and then the error is in the content and there is no JSON
pvlib/iotools/pvgis.py CHANGED
@@ -45,7 +45,7 @@ VARIABLE_MAP = {
45
45
 
46
46
  def get_pvgis_hourly(latitude, longitude, start=None, end=None,
47
47
  raddatabase=None, components=True,
48
- surface_tilt=0, surface_azimuth=0,
48
+ surface_tilt=0, surface_azimuth=180,
49
49
  outputformat='json',
50
50
  usehorizon=True, userhorizon=None,
51
51
  pvcalculation=False,
@@ -76,9 +76,15 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
76
76
  Otherwise only global irradiance is returned.
77
77
  surface_tilt: float, default: 0
78
78
  Tilt angle from horizontal plane. Ignored for two-axis tracking.
79
- surface_azimuth: float, default: 0
80
- Orientation (azimuth angle) of the (fixed) plane. 0=south, 90=west,
81
- -90: east. Ignored for tracking systems.
79
+ surface_azimuth: float, default: 180
80
+ Orientation (azimuth angle) of the (fixed) plane. Counter-clockwise
81
+ from north (north=0, south=180). This is offset 180 degrees from
82
+ the convention used by PVGIS. Ignored for tracking systems.
83
+
84
+ .. versionchanged:: 0.10.0
85
+ The `surface_azimuth` parameter now follows the pvlib convention, which
86
+ is counterclockwise from north. However, the convention used by the
87
+ PVGIS website and pvlib<=0.9.5 is offset by 180 degrees.
82
88
  usehorizon: bool, default: True
83
89
  Include effects of horizon
84
90
  userhorizon: list of float, default: None
@@ -144,6 +150,13 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
144
150
  time stamp convention, e.g., SARAH and SARAH2 provide instantaneous values,
145
151
  whereas values from ERA5 are averages for the hour.
146
152
 
153
+ Warning
154
+ -------
155
+ The azimuth orientation specified in the output metadata does not
156
+ correspond to the pvlib convention, but is offset 180 degrees. This is
157
+ despite the fact that the input parameter `surface_tilt` has to be
158
+ specified according to the pvlib convention.
159
+
147
160
  Notes
148
161
  -----
149
162
  data includes the following fields:
@@ -191,7 +204,7 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
191
204
  """ # noqa: E501
192
205
  # use requests to format the query string by passing params dictionary
193
206
  params = {'lat': latitude, 'lon': longitude, 'outputformat': outputformat,
194
- 'angle': surface_tilt, 'aspect': surface_azimuth,
207
+ 'angle': surface_tilt, 'aspect': surface_azimuth-180,
195
208
  'pvcalculation': int(pvcalculation),
196
209
  'pvtechchoice': pvtechchoice, 'mountingplace': mountingplace,
197
210
  'trackingtype': trackingtype, 'components': int(components),
@@ -315,6 +328,11 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
315
328
  metadata : dict
316
329
  metadata
317
330
 
331
+ Warning
332
+ -------
333
+ The azimuth orientation specified in the output metadata does not
334
+ correspond to the pvlib convention, but is offset 180 degrees.
335
+
318
336
  Raises
319
337
  ------
320
338
  ValueError
@@ -373,8 +391,8 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
373
391
 
374
392
 
375
393
  def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
376
- userhorizon=None, startyear=None, endyear=None, url=URL,
377
- map_variables=None, timeout=30):
394
+ userhorizon=None, startyear=None, endyear=None,
395
+ map_variables=True, url=URL, timeout=30):
378
396
  """
379
397
  Get TMY data from PVGIS.
380
398
 
@@ -400,11 +418,11 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
400
418
  first year to calculate TMY
401
419
  endyear : int, default None
402
420
  last year to calculate TMY, must be at least 10 years from first year
421
+ map_variables: bool, default True
422
+ When true, renames columns of the Dataframe to pvlib variable names
423
+ where applicable. See variable :const:`VARIABLE_MAP`.
403
424
  url : str, default: :const:`pvlib.iotools.pvgis.URL`
404
425
  base url of PVGIS API, append ``tmy`` to get TMY endpoint
405
- map_variables: bool
406
- When true, renames columns of the Dataframe to pvlib variable names
407
- where applicable. See variable const:`VARIABLE_MAP`.
408
426
  timeout : int, default 30
409
427
  time in seconds to wait for server response before timeout
410
428
 
@@ -490,14 +508,6 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
490
508
  # the response is HTTP/1.1 400 BAD REQUEST which is handled earlier
491
509
  pass
492
510
 
493
- if map_variables is None:
494
- warnings.warn(
495
- 'PVGIS variable names will be renamed to pvlib conventions by '
496
- 'default starting in pvlib 0.10.0. Specify map_variables=True '
497
- 'to enable that behavior now, or specify map_variables=False '
498
- 'to hide this warning.', pvlibDeprecationWarning
499
- )
500
- map_variables = False
501
511
  if map_variables:
502
512
  data = data.rename(columns=VARIABLE_MAP)
503
513
 
@@ -555,7 +565,7 @@ def _parse_pvgis_tmy_basic(src):
555
565
  return data, None, None, None
556
566
 
557
567
 
558
- def read_pvgis_tmy(filename, pvgis_format=None, map_variables=None):
568
+ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=True):
559
569
  """
560
570
  Read a file downloaded from PVGIS.
561
571
 
@@ -571,7 +581,7 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=None):
571
581
  ``outputformat='basic'``, please set ``pvgis_format`` to ``'basic'``.
572
582
  If ``filename`` is a buffer, then ``pvgis_format`` is required and must
573
583
  be in ``['csv', 'epw', 'json', 'basic']``.
574
- map_variables: bool
584
+ map_variables: bool, default True
575
585
  When true, renames columns of the Dataframe to pvlib variable names
576
586
  where applicable. See variable :const:`VARIABLE_MAP`.
577
587
 
@@ -653,15 +663,61 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=None):
653
663
  "'csv', or 'basic'").format(outputformat)
654
664
  raise ValueError(err_msg)
655
665
 
656
- if map_variables is None:
657
- warnings.warn(
658
- 'PVGIS variable names will be renamed to pvlib conventions by '
659
- 'default starting in pvlib 0.10.0. Specify map_variables=True '
660
- 'to enable that behavior now, or specify map_variables=False '
661
- 'to hide this warning.', pvlibDeprecationWarning
662
- )
663
- map_variables = False
664
666
  if map_variables:
665
667
  data = data.rename(columns=VARIABLE_MAP)
666
668
 
667
669
  return data, months_selected, inputs, meta
670
+
671
+
672
+ def get_pvgis_horizon(latitude, longitude, url=URL, **kwargs):
673
+ """Get horizon data from PVGIS.
674
+
675
+ Parameters
676
+ ----------
677
+ latitude : float
678
+ Latitude in degrees north
679
+ longitude : float
680
+ Longitude in degrees east
681
+ url: str, default: :const:`pvlib.iotools.pvgis.URL`
682
+ Base URL for PVGIS
683
+ kwargs:
684
+ Passed to requests.get
685
+
686
+ Returns
687
+ -------
688
+ data : pd.Series
689
+ Pandas Series of the retrived horizon elevation angles. Index is the
690
+ corresponding horizon azimuth angles.
691
+ metadata : dict
692
+ Metadata returned by PVGIS.
693
+
694
+ Notes
695
+ -----
696
+ The horizon azimuths are specified clockwise from north, e.g., south=180.
697
+ This is the standard pvlib convention, although the PVGIS website specifies
698
+ south=0.
699
+
700
+ References
701
+ ----------
702
+ .. [1] `PVGIS horizon profile tool
703
+ <https://ec.europa.eu/jrc/en/PVGIS/tools/horizon>`_
704
+ """
705
+ params = {'lat': latitude, 'lon': longitude, 'outputformat': 'json'}
706
+ res = requests.get(url + 'printhorizon', params=params, **kwargs)
707
+ if not res.ok:
708
+ try:
709
+ err_msg = res.json()
710
+ except Exception:
711
+ res.raise_for_status()
712
+ else:
713
+ raise requests.HTTPError(err_msg['message'])
714
+ json_output = res.json()
715
+ metadata = json_output['meta']
716
+ data = pd.DataFrame(json_output['outputs']['horizon_profile'])
717
+ data.columns = ['horizon_azimuth', 'horizon_elevation']
718
+ # Convert azimuth to pvlib convention (north=0, south=180)
719
+ data['horizon_azimuth'] += 180
720
+ data.set_index('horizon_azimuth', inplace=True)
721
+ data = data['horizon_elevation'] # convert to pd.Series
722
+ data = data[data.index < 360] # remove duplicate north point (0 and 360)
723
+ return data, metadata
pvlib/iotools/sodapro.py CHANGED
@@ -9,6 +9,8 @@ import io
9
9
  import warnings
10
10
 
11
11
 
12
+ URL = 'api.soda-solardata.com'
13
+
12
14
  CAMS_INTEGRATED_COLUMNS = [
13
15
  'TOA', 'Clear sky GHI', 'Clear sky BHI', 'Clear sky DHI', 'Clear sky BNI',
14
16
  'GHI', 'BHI', 'DHI', 'BNI',
@@ -44,7 +46,7 @@ SUMMATION_PERIOD_TO_TIME_STEP = {'0 year 0 month 0 day 0 h 1 min 0 s': '1min',
44
46
  def get_cams(latitude, longitude, start, end, email, identifier='mcclear',
45
47
  altitude=None, time_step='1h', time_ref='UT', verbose=False,
46
48
  integrated=False, label=None, map_variables=True,
47
- server='www.soda-is.com', timeout=30):
49
+ server=URL, timeout=30):
48
50
  """
49
51
  Retrieve time-series of radiation and/or clear-sky global, beam, and
50
52
  diffuse radiation from CAMS (see [1]_). Data is retrieved from SoDa [2]_.
@@ -91,8 +93,8 @@ def get_cams(latitude, longitude, start, end, email, identifier='mcclear',
91
93
  map_variables: bool, default: True
92
94
  When true, renames columns of the DataFrame to pvlib variable names
93
95
  where applicable. See variable :const:`VARIABLE_MAP`.
94
- server: str, default: 'www.soda-is.com'
95
- Main server (www.soda-is.com) or backup mirror server (pro.soda-is.com)
96
+ server: str, default: :const:`pvlib.iotools.sodapro.URL`
97
+ Base url of the SoDa Pro CAMS Radiation API.
96
98
  timeout : int, default: 30
97
99
  Time in seconds to wait for server response before timeout
98
100
 
@@ -344,13 +346,13 @@ def read_cams(filename, integrated=False, label=None, map_variables=True):
344
346
  all time steps except for '1M' which has a default of 'right'.
345
347
  map_variables: bool, default: True
346
348
  When true, renames columns of the Dataframe to pvlib variable names
347
- where applicable. See variable VARIABLE_MAP.
349
+ where applicable. See variable :const:`VARIABLE_MAP`.
348
350
 
349
351
  Returns
350
352
  -------
351
353
  data: pandas.DataFrame
352
- Timeseries data from CAMS Radiation or McClear
353
- :func:`pvlib.iotools.get_cams` for fields
354
+ Timeseries data from CAMS Radiation or McClear.
355
+ See :func:`pvlib.iotools.get_cams` for fields.
354
356
  metadata: dict
355
357
  Metadata available in the file.
356
358
 
pvlib/iotools/srml.py CHANGED
@@ -3,7 +3,10 @@ Radiation Monitoring Laboratory (SRML) data.
3
3
  """
4
4
  import numpy as np
5
5
  import pandas as pd
6
+ import urllib
7
+ import warnings
6
8
 
9
+ from pvlib._deprecation import deprecated
7
10
 
8
11
  # VARIABLE_MAP is a dictionary mapping SRML data element numbers to their
9
12
  # pvlib names. For most variables, only the first three digits are used,
@@ -15,7 +18,7 @@ VARIABLE_MAP = {
15
18
  '100': 'ghi',
16
19
  '201': 'dni',
17
20
  '300': 'dhi',
18
- '920': 'wind_dir',
21
+ '920': 'wind_direction',
19
22
  '921': 'wind_speed',
20
23
  '930': 'temp_air',
21
24
  '931': 'temp_dew',
@@ -24,22 +27,24 @@ VARIABLE_MAP = {
24
27
  }
25
28
 
26
29
 
27
- def read_srml(filename):
30
+ def read_srml(filename, map_variables=True):
28
31
  """
29
- Read University of Oregon SRML 1min .tsv file into pandas dataframe. The
30
- SRML is described in [1]_.
32
+ Read University of Oregon SRML 1min .tsv file into pandas dataframe.
33
+
34
+ The SRML is described in [1]_.
31
35
 
32
36
  Parameters
33
37
  ----------
34
38
  filename: str
35
39
  filepath or url to read for the tsv file.
40
+ map_variables: bool, default: True
41
+ When true, renames columns of the DataFrame to pvlib variable names
42
+ where applicable. See variable :const:`VARIABLE_MAP`.
36
43
 
37
44
  Returns
38
45
  -------
39
46
  data: Dataframe
40
- A dataframe with datetime index and all of the variables listed
41
- in the `VARIABLE_MAP` dict inside of the map_columns function,
42
- along with their associated quality control flags.
47
+ A dataframe with datetime index
43
48
 
44
49
  Notes
45
50
  -----
@@ -50,21 +55,22 @@ def read_srml(filename):
50
55
  the time of the row until the time of the next row. This is consistent
51
56
  with pandas' default labeling behavior.
52
57
 
53
- See SRML's `Archival Files`_ page for more information.
54
-
55
- .. _Archival Files: http://solardat.uoregon.edu/ArchivalFiles.html
58
+ See [2]_ for more information concerning the file format.
56
59
 
57
60
  References
58
61
  ----------
59
62
  .. [1] University of Oregon Solar Radiation Monitoring Laboratory
60
63
  `http://solardat.uoregon.edu/ <http://solardat.uoregon.edu/>`_
64
+ .. [2] `Archival (short interval) data files
65
+ <http://solardat.uoregon.edu/ArchivalFiles.html>`_
61
66
  """
62
67
  tsv_data = pd.read_csv(filename, delimiter='\t')
63
- data = format_index(tsv_data)
68
+ data = _format_index(tsv_data)
64
69
  # Drop day of year and time columns
65
70
  data = data[data.columns[2:]]
66
71
 
67
- data = data.rename(columns=map_columns)
72
+ if map_variables:
73
+ data = data.rename(columns=_map_columns)
68
74
 
69
75
  # Quality flag columns are all labeled 0 in the original data. They
70
76
  # appear immediately after their associated variable and are suffixed
@@ -90,7 +96,7 @@ def read_srml(filename):
90
96
  return data
91
97
 
92
98
 
93
- def map_columns(col):
99
+ def _map_columns(col):
94
100
  """Map data element numbers to pvlib names.
95
101
 
96
102
  Parameters
@@ -118,7 +124,7 @@ def map_columns(col):
118
124
  return col
119
125
 
120
126
 
121
- def format_index(df):
127
+ def _format_index(df):
122
128
  """Create a datetime index from day of year, and time columns.
123
129
 
124
130
  Parameters
@@ -166,9 +172,12 @@ def format_index(df):
166
172
  return df
167
173
 
168
174
 
169
- def read_srml_month_from_solardat(station, year, month, filetype='PO'):
170
- """Request a month of SRML data from solardat and read it into
171
- a Dataframe. The SRML is described in [1]_.
175
+ @deprecated('0.10.0', alternative='pvlib.iotools.get_srml', removal='0.11.0')
176
+ def read_srml_month_from_solardat(station, year, month, filetype='PO',
177
+ map_variables=True):
178
+ """Request a month of SRML data and read it into a Dataframe.
179
+
180
+ The SRML is described in [1]_.
172
181
 
173
182
  Parameters
174
183
  ----------
@@ -180,6 +189,9 @@ def read_srml_month_from_solardat(station, year, month, filetype='PO'):
180
189
  Month to request data for.
181
190
  filetype: string
182
191
  SRML file type to gather. See notes for explanation.
192
+ map_variables: bool, default: True
193
+ When true, renames columns of the DataFrame to pvlib variable names
194
+ where applicable. See variable :const:`VARIABLE_MAP`.
183
195
 
184
196
  Returns
185
197
  -------
@@ -214,5 +226,96 @@ def read_srml_month_from_solardat(station, year, month, filetype='PO'):
214
226
  year=year % 100,
215
227
  month=month)
216
228
  url = "http://solardat.uoregon.edu/download/Archive/"
217
- data = read_srml(url + file_name)
229
+ data = read_srml(url + file_name, map_variables=map_variables)
218
230
  return data
231
+
232
+
233
+ def get_srml(station, start, end, filetype='PO', map_variables=True,
234
+ url="http://solardat.uoregon.edu/download/Archive/"):
235
+ """Request data from UoO SRML and read it into a Dataframe.
236
+
237
+ The University of Oregon Solar Radiation Monitoring Laboratory (SRML) is
238
+ described in [1]_. A list of stations can be found in [2]_.
239
+
240
+ Data is returned for the entire months between and including start and end.
241
+
242
+ Parameters
243
+ ----------
244
+ station : str
245
+ Two letter station abbreviation.
246
+ start : datetime like
247
+ First day of the requested period
248
+ end : datetime like
249
+ Last day of the requested period
250
+ filetype : string, default: 'PO'
251
+ SRML file type to gather. See notes for explanation.
252
+ map_variables : bool, default: True
253
+ When true, renames columns of the DataFrame to pvlib variable names
254
+ where applicable. See variable :const:`VARIABLE_MAP`.
255
+ url : str, default: 'http://solardat.uoregon.edu/download/Archive/'
256
+ API endpoint URL
257
+
258
+ Returns
259
+ -------
260
+ data : pd.DataFrame
261
+ Dataframe with data from SRML.
262
+ meta : dict
263
+ Metadata.
264
+
265
+ Notes
266
+ -----
267
+ File types designate the time interval of a file and if it contains
268
+ raw or processed data. For instance, `RO` designates raw, one minute
269
+ data and `PO` designates processed one minute data. The availability
270
+ of file types varies between sites. Below is a table of file types
271
+ and their time intervals. See [1] for site information.
272
+
273
+ ============= ============ ==================
274
+ time interval raw filetype processed filetype
275
+ ============= ============ ==================
276
+ 1 minute RO PO
277
+ 5 minute RF PF
278
+ 15 minute RQ PQ
279
+ hourly RH PH
280
+ ============= ============ ==================
281
+
282
+ Warning
283
+ -------
284
+ SRML data has nighttime data prefilled with 0s through the end of the
285
+ current month (i.e., values are provided for data in the future).
286
+
287
+ References
288
+ ----------
289
+ .. [1] University of Oregon Solar Radiation Measurement Laboratory
290
+ `http://solardat.uoregon.edu/ <http://solardat.uoregon.edu/>`_
291
+ .. [2] Station ID codes - Solar Radiation Measurement Laboratory
292
+ `http://solardat.uoregon.edu/StationIDCodes.html
293
+ <http://solardat.uoregon.edu/StationIDCodes.html>`_
294
+ """
295
+ # Use pd.to_datetime so that strings (e.g. '2021-01-01') are accepted
296
+ start = pd.to_datetime(start)
297
+ end = pd.to_datetime(end)
298
+
299
+ # Generate list of months
300
+ months = pd.date_range(
301
+ start, end.replace(day=1) + pd.DateOffset(months=1), freq='1M')
302
+ months_str = months.strftime('%y%m')
303
+
304
+ # Generate list of filenames
305
+ filenames = [f"{station}{filetype}{m}.txt" for m in months_str]
306
+
307
+ dfs = [] # Initialize list of monthly dataframes
308
+ for f in filenames:
309
+ try:
310
+ dfi = read_srml(url + f, map_variables=map_variables)
311
+ dfs.append(dfi)
312
+ except urllib.error.HTTPError:
313
+ warnings.warn(f"The following file was not found: {f}")
314
+
315
+ data = pd.concat(dfs, axis='rows')
316
+
317
+ meta = {'filetype': filetype,
318
+ 'station': station,
319
+ 'filenames': filenames}
320
+
321
+ return data, meta
pvlib/iotools/surfrad.py CHANGED
@@ -150,7 +150,7 @@ def read_surfrad(filename, map_variables=True):
150
150
  header=None, names=SURFRAD_COLUMNS)
151
151
  file_buffer.close()
152
152
 
153
- data = format_index(data)
153
+ data = _format_index(data)
154
154
  missing = data == -9999.9
155
155
  data = data.where(~missing, np.NaN)
156
156
 
@@ -159,7 +159,7 @@ def read_surfrad(filename, map_variables=True):
159
159
  return data, metadata
160
160
 
161
161
 
162
- def format_index(data):
162
+ def _format_index(data):
163
163
  """Create UTC localized DatetimeIndex for the dataframe.
164
164
 
165
165
  Parameters