pvlib 0.10.3__py3-none-any.whl → 0.10.5__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 (53) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/bifacial/utils.py +2 -1
  3. pvlib/clearsky.py +7 -8
  4. pvlib/iam.py +3 -3
  5. pvlib/inverter.py +3 -3
  6. pvlib/iotools/__init__.py +2 -0
  7. pvlib/iotools/solargis.py +214 -0
  8. pvlib/iotools/solcast.py +2 -7
  9. pvlib/iotools/solrad.py +121 -23
  10. pvlib/iotools/srml.py +12 -12
  11. pvlib/iotools/surfrad.py +2 -2
  12. pvlib/irradiance.py +28 -22
  13. pvlib/location.py +3 -1
  14. pvlib/modelchain.py +10 -9
  15. pvlib/pvarray.py +127 -0
  16. pvlib/pvsystem.py +52 -43
  17. pvlib/scaling.py +4 -2
  18. pvlib/shading.py +110 -0
  19. pvlib/singlediode.py +37 -9
  20. pvlib/snow.py +3 -1
  21. pvlib/solarposition.py +38 -30
  22. pvlib/spa.py +3 -11
  23. pvlib/spectrum/mismatch.py +2 -1
  24. pvlib/temperature.py +3 -2
  25. pvlib/tests/bifacial/test_utils.py +6 -5
  26. pvlib/tests/conftest.py +13 -14
  27. pvlib/tests/iotools/test_sodapro.py +2 -1
  28. pvlib/tests/iotools/test_solargis.py +68 -0
  29. pvlib/tests/iotools/test_solcast.py +2 -2
  30. pvlib/tests/iotools/test_solrad.py +58 -7
  31. pvlib/tests/iotools/test_srml.py +7 -14
  32. pvlib/tests/test_clearsky.py +1 -1
  33. pvlib/tests/test_irradiance.py +24 -8
  34. pvlib/tests/test_location.py +1 -1
  35. pvlib/tests/test_modelchain.py +37 -26
  36. pvlib/tests/test_pvarray.py +25 -0
  37. pvlib/tests/test_pvsystem.py +76 -104
  38. pvlib/tests/test_shading.py +130 -11
  39. pvlib/tests/test_singlediode.py +68 -10
  40. pvlib/tests/test_snow.py +1 -1
  41. pvlib/tests/test_solarposition.py +121 -7
  42. pvlib/tests/test_spa.py +5 -15
  43. pvlib/tests/test_temperature.py +4 -4
  44. pvlib/tests/test_tracking.py +2 -2
  45. pvlib/tracking.py +8 -38
  46. pvlib/version.py +1 -5
  47. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/METADATA +9 -33
  48. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/RECORD +52 -51
  49. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/WHEEL +1 -1
  50. pvlib/spa_c_files/SPA_NOTICE.md +0 -39
  51. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/AUTHORS.md +0 -0
  52. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/LICENSE +0 -0
  53. {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/top_level.txt +0 -0
pvlib/__init__.py CHANGED
@@ -14,6 +14,7 @@ from pvlib import ( # noqa: F401
14
14
  ivtools,
15
15
  location,
16
16
  modelchain,
17
+ pvarray,
17
18
  pvsystem,
18
19
  scaling,
19
20
  shading,
pvlib/bifacial/utils.py CHANGED
@@ -4,6 +4,7 @@ modeling.
4
4
  """
5
5
  import numpy as np
6
6
  from pvlib.tools import sind, cosd, tand
7
+ from scipy.integrate import trapezoid
7
8
 
8
9
 
9
10
  def _solar_projection_tangent(solar_zenith, solar_azimuth, surface_azimuth):
@@ -220,7 +221,7 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10,
220
221
  vf = vf_ground_sky_2d(r, gcr, z, pitch, height, max_rows)
221
222
  fz_sky[:, k] = vf[:, 0] # remove spurious rotation dimension
222
223
  # calculate the integrated view factor for all of the ground between rows
223
- return np.trapz(fz_sky, z, axis=0)
224
+ return trapezoid(fz_sky, z, axis=0)
224
225
 
225
226
 
226
227
  def _vf_poly(surface_tilt, gcr, x, delta):
pvlib/clearsky.py CHANGED
@@ -25,11 +25,11 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
25
25
  Implements the Ineichen and Perez clear sky model for global
26
26
  horizontal irradiance (GHI), direct normal irradiance (DNI), and
27
27
  calculates the clear-sky diffuse horizontal (DHI) component as the
28
- difference between GHI and DNI*cos(zenith) as presented in [1, 2]. A
28
+ difference between GHI and DNI*cos(zenith) as presented in [1]_ [2]_. A
29
29
  report on clear sky models found the Ineichen/Perez model to have
30
- excellent performance with a minimal input data set [3].
30
+ excellent performance with a minimal input data set [3]_.
31
31
 
32
- Default values for monthly Linke turbidity provided by SoDa [4, 5].
32
+ Default values for monthly Linke turbidity provided by SoDa [4]_, [5]_.
33
33
 
34
34
  Parameters
35
35
  -----------
@@ -80,12 +80,12 @@ def ineichen(apparent_zenith, airmass_absolute, linke_turbidity,
80
80
  Clear Sky Models: Implementation and Analysis", Sandia National
81
81
  Laboratories, SAND2012-2389, 2012.
82
82
 
83
- .. [4] http://www.soda-is.com/eng/services/climat_free_eng.php#c5 (obtained
84
- July 17, 2012).
83
+ .. [4] https://www.soda-pro.com/help/general-knowledge/linke-turbidity-factor
84
+ (accessed February 2, 2024).
85
85
 
86
86
  .. [5] J. Remund, et. al., "Worldwide Linke Turbidity Information", Proc.
87
87
  ISES Solar World Congress, June 2003. Goteborg, Sweden.
88
- '''
88
+ ''' # noqa: E501
89
89
 
90
90
  # ghi is calculated using either the equations in [1] by setting
91
91
  # perez_enhancement=False (default behavior) or using the model
@@ -993,8 +993,7 @@ def bird(zenith, airmass_relative, aod380, aod500, precipitable_water,
993
993
  .. [3] `NREL Bird Clear Sky Model <http://rredc.nrel.gov/solar/models/
994
994
  clearsky/>`_
995
995
 
996
- .. [4] `SERI/TR-642-761 <http://rredc.nrel.gov/solar/pubs/pdfs/
997
- tr-642-761.pdf>`_
996
+ .. [4] `SERI/TR-642-761 <https://www.nrel.gov/docs/legosti/old/761.pdf>`_
998
997
 
999
998
  .. [5] `Error Reports <http://rredc.nrel.gov/solar/models/clearsky/
1000
999
  error_reports.html>`_
pvlib/iam.py CHANGED
@@ -69,9 +69,9 @@ def ashrae(aoi, b=0.05):
69
69
 
70
70
  .. [2] ASHRAE standard 93-77
71
71
 
72
- .. [3] PVsyst Contextual Help.
73
- https://files.pvsyst.com/help/index.html?iam_loss.htm retrieved on
74
- October 14, 2019
72
+ .. [3] PVsyst 7 Help.
73
+ https://www.pvsyst.com/help/index.html?iam_loss.htm retrieved on
74
+ January 30, 2024
75
75
 
76
76
  See Also
77
77
  --------
pvlib/inverter.py CHANGED
@@ -532,9 +532,9 @@ def fit_sandia(ac_power, dc_power, dc_voltage, dc_voltage_level, p_ac_0, p_nt):
532
532
  p_s0 = solve_quad(a, b, c)
533
533
 
534
534
  # Add values to dataframe at index d
535
- coeffs['a'][d] = a
536
- coeffs['p_dc'][d] = p_dc
537
- coeffs['p_s0'][d] = p_s0
535
+ coeffs.loc[d, 'a'] = a
536
+ coeffs.loc[d, 'p_dc'] = p_dc
537
+ coeffs.loc[d, 'p_s0'] = p_s0
538
538
 
539
539
  b_dc0, b_dc1, c1 = extract_c(x_d, coeffs['p_dc'])
540
540
  b_s0, b_s1, c2 = extract_c(x_d, coeffs['p_s0'])
pvlib/iotools/__init__.py CHANGED
@@ -8,6 +8,7 @@ from pvlib.iotools.midc import read_midc # noqa: F401
8
8
  from pvlib.iotools.midc import read_midc_raw_data_from_nrel # noqa: F401
9
9
  from pvlib.iotools.crn import read_crn # noqa: F401
10
10
  from pvlib.iotools.solrad import read_solrad # noqa: F401
11
+ from pvlib.iotools.solrad import get_solrad # noqa: F401
11
12
  from pvlib.iotools.psm3 import get_psm3 # noqa: F401
12
13
  from pvlib.iotools.psm3 import read_psm3 # noqa: F401
13
14
  from pvlib.iotools.psm3 import parse_psm3 # noqa: F401
@@ -33,3 +34,4 @@ from pvlib.iotools.solcast import get_solcast_forecast # noqa: F401
33
34
  from pvlib.iotools.solcast import get_solcast_live # noqa: F401
34
35
  from pvlib.iotools.solcast import get_solcast_historic # noqa: F401
35
36
  from pvlib.iotools.solcast import get_solcast_tmy # noqa: F401
37
+ from pvlib.iotools.solargis import get_solargis # noqa: F401
@@ -0,0 +1,214 @@
1
+ """Functions to retrieve and parse irradiance data from Solargis."""
2
+
3
+ import pandas as pd
4
+ import requests
5
+ from dataclasses import dataclass
6
+ import io
7
+
8
+ URL = 'https://solargis.info/ws/rest/datadelivery/request'
9
+
10
+
11
+ TIME_RESOLUTION_MAP = {
12
+ 5: 'MIN_5', 10: 'MIN_10', 15: 'MIN_15', 30: 'MIN_30', 60: 'HOURLY',
13
+ 'PT05M': 'MIN_5', 'PT5M': 'MIN_5', 'PT10M': 'MIN_10', 'PT15M': 'MIN_15',
14
+ 'PT30': 'MIN_30', 'PT60M': 'HOURLY', 'PT1H': 'HOURLY', 'P1D': 'DAILY',
15
+ 'P1M': 'MONTHLY', 'P1Y': 'YEARLY'}
16
+
17
+
18
+ @dataclass
19
+ class ParameterMap:
20
+ solargis_name: str
21
+ pvlib_name: str
22
+ conversion: callable = lambda x: x
23
+
24
+
25
+ # define the conventions between Solargis and pvlib nomenclature and units
26
+ VARIABLE_MAP = [
27
+ # Irradiance (unit varies based on time resolution)
28
+ ParameterMap('GHI', 'ghi'),
29
+ ParameterMap('GHI_C', 'ghi_clear'), # this is stated in documentation
30
+ ParameterMap('GHIc', 'ghi_clear'), # this is used in practice
31
+ ParameterMap('DNI', 'dni'),
32
+ ParameterMap('DNI_C', 'dni_clear'),
33
+ ParameterMap('DNIc', 'dni_clear'),
34
+ ParameterMap('DIF', 'dhi'),
35
+ ParameterMap('GTI', 'poa_global'),
36
+ ParameterMap('GTI_C', 'poa_global_clear'),
37
+ ParameterMap('GTIc', 'poa_global_clear'),
38
+ # Solar position
39
+ ParameterMap('SE', 'solar_elevation'),
40
+ # SA -> solar_azimuth (degrees) (different convention)
41
+ ParameterMap("SA", "solar_azimuth", lambda x: x + 180),
42
+ # Weather / atmospheric parameters
43
+ ParameterMap('TEMP', 'temp_air'),
44
+ ParameterMap('TD', 'temp_dew'),
45
+ # surface_pressure (hPa) -> pressure (Pa)
46
+ ParameterMap('AP', 'pressure', lambda x: x*100),
47
+ ParameterMap('RH', 'relative_humidity'),
48
+ ParameterMap('WS', 'wind_speed'),
49
+ ParameterMap('WD', 'wind_direction'),
50
+ ParameterMap('INC', 'aoi'), # angle of incidence of direct irradiance
51
+ # precipitable_water (kg/m2) -> precipitable_water (cm)
52
+ ParameterMap('PWAT', 'precipitable_water', lambda x: x/10),
53
+ ]
54
+
55
+ METADATA_FIELDS = [
56
+ 'issued', 'site name', 'latitude', 'longitude', 'elevation',
57
+ 'summarization type', 'summarization period'
58
+ ]
59
+
60
+
61
+ # Variables that use "-9" as nan values
62
+ NA_9_COLUMNS = ['GHI', 'GHIc', 'DNI', 'DNIc', 'DIF', 'GTI', 'GIc', 'KT', 'PAR',
63
+ 'PREC', 'PWAT', 'SDWE', 'SFWE']
64
+
65
+
66
+ def get_solargis(latitude, longitude, start, end, variables, api_key,
67
+ time_resolution, timestamp_type='center', tz='GMT+00',
68
+ terrain_shading=True, url=URL, map_variables=True,
69
+ timeout=30):
70
+ """
71
+ Retrieve irradiance time series data from Solargis.
72
+
73
+ The Solargis [1]_ API is described in [2]_.
74
+
75
+ Parameters
76
+ ----------
77
+ latitude: float
78
+ In decimal degrees, between -90 and 90, north is positive (ISO 19115)
79
+ longitude: float
80
+ In decimal degrees, between -180 and 180, east is positive (ISO 19115)
81
+ start : datetime-like
82
+ Start date of time series.
83
+ end : datetime-like
84
+ End date of time series.
85
+ variables : list
86
+ List of variables to request, see [2]_ for options.
87
+ api_key : str
88
+ API key.
89
+ time_resolution : str, {'PT05M', 'PT10M', 'PT15M', 'PT30', 'PT1H', 'P1D', 'P1M', 'P1Y'}
90
+ Time resolution as an integer number of minutes (e.g. 5, 60)
91
+ or an ISO 8601 duration string (e.g. "PT05M", "PT60M", "P1M").
92
+ timestamp_type : {'start', 'center', 'end'}, default: 'center'
93
+ Labeling of time stamps of the return data.
94
+ tz : str, default : 'GMT+00'
95
+ Timezone of `start` and `end` in the format "GMT+hh" or "GMT-hh".
96
+ terrain_shading : boolean, default: True
97
+ Whether to account for horizon shading.
98
+ url : str, default : :const:`pvlib.iotools.solargis.URL`
99
+ Base url of Solargis API.
100
+ map_variables : boolean, default: True
101
+ When true, renames columns of the Dataframe to pvlib variable names
102
+ where applicable. See variable :const:`VARIABLE_MAP`.
103
+ timeout : int or float, default: 30
104
+ Time in seconds to wait for server response before timeout
105
+
106
+ Returns
107
+ -------
108
+ data : DataFrame
109
+ DataFrame containing time series data.
110
+ meta : dict
111
+ Dictionary containing metadata.
112
+
113
+ Raises
114
+ ------
115
+ requests.HTTPError
116
+ A message from the Solargis server if the request is rejected
117
+
118
+ Notes
119
+ -----
120
+ Each XML request is limited to retrieving 31 days of data.
121
+
122
+ The variable units depends on the time frequency, e.g., the unit for
123
+ sub-hourly irradiance data is :math:`W/m^2`, for hourly data it is
124
+ :math:`Wh/m^2`, and for daily data it is :math:`kWh/m^2`.
125
+
126
+ References
127
+ ----------
128
+ .. [1] `Solargis <https://solargis.com>`_
129
+ .. [2] `Solargis API User Guide
130
+ <https://solargis.atlassian.net/wiki/spaces/public/pages/7602367/Solargis+API+User+Guide>`_
131
+
132
+ Examples
133
+ --------
134
+ >>> # Retrieve two days of irradiance data from Solargis
135
+ >>> data, meta = response = pvlib.iotools.get_solargis(
136
+ >>> latitude=48.61259, longitude=20.827079,
137
+ >>> start='2022-01-01', end='2022-01-02',
138
+ >>> variables=['GHI', 'DNI'], time_resolution='PT05M', api_key='demo')
139
+ """ # noqa: E501
140
+ # Use pd.to_datetime so that strings (e.g. '2021-01-01') are accepted
141
+ start = pd.to_datetime(start)
142
+ end = pd.to_datetime(end)
143
+
144
+ headers = {'Content-Type': 'application/xml'}
145
+
146
+ # Solargis recommends creating a unique site_id for each location request.
147
+ # The site_id does not impact the data retrieval and is used for debugging.
148
+ site_id = f"latitude_{latitude}_longitude_{longitude}"
149
+
150
+ request_xml = f'''<ws:dataDeliveryRequest
151
+ dateFrom="{start.strftime('%Y-%m-%d')}"
152
+ dateTo="{end.strftime('%Y-%m-%d')}"
153
+ xmlns="http://geomodel.eu/schema/data/request"
154
+ xmlns:ws="http://geomodel.eu/schema/ws/data"
155
+ xmlns:geo="http://geomodel.eu/schema/common/geo"
156
+ xmlns:pv="http://geomodel.eu/schema/common/pv"
157
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
158
+ <site id="{site_id}" name="" lat="{latitude}" lng="{longitude}">
159
+ </site>
160
+ <processing key="{' '.join(variables)}"
161
+ summarization="{TIME_RESOLUTION_MAP.get(time_resolution, time_resolution).upper()}"
162
+ terrainShading="{str(terrain_shading).lower()}">
163
+ <timestampType>{timestamp_type.upper()}</timestampType>
164
+ <timeZone>{tz}</timeZone>
165
+ </processing>
166
+ </ws:dataDeliveryRequest>''' # noqa: E501
167
+
168
+ response = requests.post(url + "?key=" + api_key, headers=headers,
169
+ data=request_xml.encode('utf8'), timeout=timeout)
170
+
171
+ if response.ok is False:
172
+ raise requests.HTTPError(response.json())
173
+
174
+ # Parse metadata
175
+ header = pd.read_xml(io.StringIO(response.text), parser='etree')
176
+ meta_lines = header['metadata'].iloc[0].split('#')
177
+ meta_lines = [line.strip() for line in meta_lines]
178
+ meta = {}
179
+ for line in meta_lines:
180
+ if ':' in line:
181
+ key = line.split(':')[0].lower()
182
+ if key in METADATA_FIELDS:
183
+ meta[key] = ':'.join(line.split(':')[1:])
184
+ meta['latitude'] = float(meta['latitude'])
185
+ meta['longitude'] = float(meta['longitude'])
186
+ meta['altitude'] = float(meta.pop('elevation').replace('m a.s.l.', ''))
187
+
188
+ # Parse data
189
+ data = pd.read_xml(io.StringIO(response.text), xpath='.//doc:row',
190
+ namespaces={'doc': 'http://geomodel.eu/schema/ws/data'},
191
+ parser='etree')
192
+ data.index = pd.to_datetime(data['dateTime'])
193
+ # when requesting one variable, it is necessary to convert dataframe to str
194
+ data = data['values'].astype(str).str.split(' ', expand=True)
195
+ data = data.astype(float)
196
+ data.columns = header['columns'].iloc[0].split()
197
+
198
+ # Replace "-9" with nan values for specific columns
199
+ for variable in data.columns:
200
+ if variable in NA_9_COLUMNS:
201
+ data[variable] = data[variable].replace(-9, pd.NA)
202
+
203
+ # rename and convert variables
204
+ if map_variables:
205
+ for variable in VARIABLE_MAP:
206
+ if variable.solargis_name in data.columns:
207
+ data.rename(
208
+ columns={variable.solargis_name: variable.pvlib_name},
209
+ inplace=True
210
+ )
211
+ data[variable.pvlib_name] = data[
212
+ variable.pvlib_name].apply(variable.conversion)
213
+
214
+ return data, meta
pvlib/iotools/solcast.py CHANGED
@@ -35,7 +35,7 @@ VARIABLE_MAP = [
35
35
  "azimuth", "solar_azimuth", lambda x: -x % 360
36
36
  ),
37
37
  # precipitable_water (kg/m2) -> precipitable_water (cm)
38
- ParameterMap("precipitable_water", "precipitable_water", lambda x: x*10),
38
+ ParameterMap("precipitable_water", "precipitable_water", lambda x: x/10),
39
39
  # zenith -> solar_zenith
40
40
  ParameterMap("zenith", "solar_zenith"),
41
41
  # clearsky
@@ -413,14 +413,9 @@ def _solcast2pvlib(data):
413
413
  a pandas.DataFrame with the data cast to pvlib's conventions
414
414
  """
415
415
  # move from period_end to period_middle as per pvlib convention
416
- # to support Pandas 0.25 we cast PTXX to XX as ISO8601
417
- # durations without days aren't supported:
418
- # https://github.com/pandas-dev/pandas/pull/37159\
419
- # Can remove once minimum supported Pandas version is >=1.2
420
- periods = data.period.str.replace("PT", "").str.replace("M", "m")
421
416
 
422
417
  data["period_mid"] = pd.to_datetime(
423
- data.period_end) - pd.to_timedelta(periods) / 2
418
+ data.period_end) - pd.to_timedelta(data.period.values) / 2
424
419
  data = data.set_index("period_mid").drop(columns=["period_end", "period"])
425
420
 
426
421
  # rename and convert variables
pvlib/iotools/solrad.py CHANGED
@@ -1,8 +1,9 @@
1
- """Functions to read data from the NOAA SOLRAD network.
2
- """
1
+ """Functions to read data from the NOAA SOLRAD network."""
3
2
 
4
- import numpy as np
5
3
  import pandas as pd
4
+ import warnings
5
+ import requests
6
+ import io
6
7
 
7
8
  # pvlib conventions
8
9
  BASE_HEADERS = (
@@ -49,8 +50,15 @@ MADISON_DTYPES = [
49
50
 
50
51
  def read_solrad(filename):
51
52
  """
52
- Read NOAA SOLRAD fixed-width file into pandas dataframe. The SOLRAD
53
- network is described in [1]_ and [2]_.
53
+ Read NOAA SOLRAD fixed-width file into pandas dataframe.
54
+
55
+ The SOLRAD network is described in [1]_ and [2]_.
56
+
57
+ .. versionchanged:: 0.10.4
58
+ The function now returns a tuple where the first element is a dataframe
59
+ and the second element is a dictionary containing metadata. Previous
60
+ versions of this function only returned a dataframe.
61
+
54
62
 
55
63
  Parameters
56
64
  ----------
@@ -62,6 +70,12 @@ def read_solrad(filename):
62
70
  data: Dataframe
63
71
  A dataframe with DatetimeIndex and all of the variables in the
64
72
  file.
73
+ metadata : dict
74
+ Metadata.
75
+
76
+ See Also
77
+ --------
78
+ get_solrad
65
79
 
66
80
  Notes
67
81
  -----
@@ -91,19 +105,30 @@ def read_solrad(filename):
91
105
  widths = WIDTHS
92
106
  dtypes = DTYPES
93
107
 
108
+ meta = {}
109
+
110
+ if str(filename).startswith('ftp') or str(filename).startswith('http'):
111
+ response = requests.get(filename)
112
+ response.raise_for_status()
113
+ file_buffer = io.StringIO(response.content.decode())
114
+ else:
115
+ with open(str(filename), 'r') as file_buffer:
116
+ file_buffer = io.StringIO(file_buffer.read())
117
+
118
+ # The first line has the name of the station, and the second gives the
119
+ # station's latitude, longitude, elevation above mean sea level in meters,
120
+ # and the displacement in hours from local standard time.
121
+ meta['station_name'] = file_buffer.readline().strip()
122
+
123
+ meta_line = file_buffer.readline().split()
124
+ meta['latitude'] = float(meta_line[0])
125
+ meta['longitude'] = float(meta_line[1])
126
+ meta['altitude'] = float(meta_line[2])
127
+ meta['TZ'] = int(meta_line[3])
128
+
94
129
  # read in data
95
- data = pd.read_fwf(filename, header=None, skiprows=2, names=names,
96
- widths=widths, na_values=-9999.9)
97
-
98
- # loop here because dtype kwarg not supported in read_fwf until 0.20
99
- for (col, _dtype) in zip(data.columns, dtypes):
100
- ser = data[col].astype(_dtype)
101
- if _dtype == 'float64':
102
- # older verions of pandas/numpy read '-9999.9' as
103
- # -9999.8999999999996 and fail to set nan in read_fwf,
104
- # so manually set nan
105
- ser = ser.where(ser > -9999, other=np.nan)
106
- data[col] = ser
130
+ data = pd.read_fwf(file_buffer, header=None, names=names,
131
+ widths=widths, na_values=-9999.9, dtypes=dtypes)
107
132
 
108
133
  # set index
109
134
  # columns do not have leading 0s, so must zfill(2) to comply
@@ -114,10 +139,83 @@ def read_solrad(filename):
114
139
  data['year'].astype(str) + dts['month'] + dts['day'] + dts['hour'] +
115
140
  dts['minute'], format='%Y%m%d%H%M', utc=True)
116
141
  data = data.set_index(dtindex)
117
- try:
118
- # to_datetime(utc=True) does not work in older versions of pandas
119
- data = data.tz_localize('UTC')
120
- except TypeError:
121
- pass
122
142
 
123
- return data
143
+ return data, meta
144
+
145
+
146
+ def get_solrad(station, start, end,
147
+ url="https://gml.noaa.gov/aftp/data/radiation/solrad/"):
148
+ """Request data from NOAA SOLRAD and read it into a Dataframe.
149
+
150
+ A list of stations and their descriptions can be found in [1]_,
151
+ The data files are described in [2]_.
152
+
153
+ Data is returned for complete days, including ``start`` and ``end``.
154
+
155
+ Parameters
156
+ ----------
157
+ station : str
158
+ Three letter station abbreviation.
159
+ start : datetime-like
160
+ First day of the requested period
161
+ end : datetime-like
162
+ Last day of the requested period
163
+ url : str, default: 'https://gml.noaa.gov/aftp/data/radiation/solrad/'
164
+ API endpoint URL
165
+
166
+ Returns
167
+ -------
168
+ data : pd.DataFrame
169
+ Dataframe with data from SOLRAD.
170
+ meta : dict
171
+ Metadata.
172
+
173
+ See Also
174
+ --------
175
+ read_solrad
176
+
177
+ Notes
178
+ -----
179
+ Recent SOLRAD data is 1-minute averages. Prior to 2015-01-01, it was
180
+ 3-minute averages.
181
+
182
+ References
183
+ ----------
184
+ .. [1] https://gml.noaa.gov/grad/solrad/index.html
185
+ .. [2] https://gml.noaa.gov/aftp/data/radiation/solrad/README_SOLRAD.txt
186
+
187
+ Examples
188
+ --------
189
+ >>> # Retrieve one month of irradiance data from the ABQ SOLRAD station
190
+ >>> data, metadata = pvlib.iotools.get_solrad(
191
+ >>> station='abq', start="2020-01-01", end="2020-01-31")
192
+ """
193
+ # Use pd.to_datetime so that strings (e.g. '2021-01-01') are accepted
194
+ start = pd.to_datetime(start)
195
+ end = pd.to_datetime(end)
196
+
197
+ # Generate list of filenames
198
+ dates = pd.date_range(start.floor('d'), end, freq='d')
199
+ station = station.lower()
200
+ filenames = [
201
+ f"{station}/{d.year}/{station}{d.strftime('%y')}{d.dayofyear:03}.dat"
202
+ for d in dates
203
+ ]
204
+
205
+ dfs = [] # Initialize list of monthly dataframes
206
+ for f in filenames:
207
+ try:
208
+ dfi, file_metadata = read_solrad(url + f)
209
+ dfs.append(dfi)
210
+ except requests.exceptions.HTTPError:
211
+ warnings.warn(f"The following file was not found: {f}")
212
+
213
+ data = pd.concat(dfs, axis='rows')
214
+
215
+ meta = {'station': station,
216
+ 'filenames': filenames,
217
+ # all file should have the same metadata, so just merge in the
218
+ # metadata from the last file
219
+ **file_metadata}
220
+
221
+ return data, meta
pvlib/iotools/srml.py CHANGED
@@ -12,7 +12,7 @@ from pvlib._deprecation import deprecated
12
12
  # pvlib names. For most variables, only the first three digits are used,
13
13
  # the fourth indicating the instrument. Spectral data (7xxx) uses all
14
14
  # four digits to indicate the variable. See a full list of data element
15
- # numbers `here. <http://solardat.uoregon.edu/DataElementNumbers.html>`_
15
+ # numbers `here. <http://solardata.uoregon.edu/DataElementNumbers.html>`_
16
16
 
17
17
  VARIABLE_MAP = {
18
18
  '100': 'ghi',
@@ -60,9 +60,9 @@ def read_srml(filename, map_variables=True):
60
60
  References
61
61
  ----------
62
62
  .. [1] University of Oregon Solar Radiation Monitoring Laboratory
63
- `http://solardat.uoregon.edu/ <http://solardat.uoregon.edu/>`_
63
+ http://solardata.uoregon.edu/
64
64
  .. [2] `Archival (short interval) data files
65
- <http://solardat.uoregon.edu/ArchivalFiles.html>`_
65
+ <http://solardata.uoregon.edu/ArchivalFiles.html>`_
66
66
  """
67
67
  tsv_data = pd.read_csv(filename, delimiter='\t')
68
68
  data = _format_index(tsv_data)
@@ -92,7 +92,7 @@ def read_srml(filename, map_variables=True):
92
92
  # Mask data marked with quality flag 99 (bad or missing data)
93
93
  for col in columns[::2]:
94
94
  missing = data[col + '_flag'] == 99
95
- data[col] = data[col].where(~(missing), np.NaN)
95
+ data[col] = data[col].where(~(missing), np.nan)
96
96
  return data
97
97
 
98
98
 
@@ -175,7 +175,8 @@ def _format_index(df):
175
175
  @deprecated('0.10.0', alternative='pvlib.iotools.get_srml', removal='0.11.0')
176
176
  def read_srml_month_from_solardat(station, year, month, filetype='PO',
177
177
  map_variables=True):
178
- """Request a month of SRML data and read it into a Dataframe.
178
+ """
179
+ Request a month of SRML data and read it into a Dataframe.
179
180
 
180
181
  The SRML is described in [1]_.
181
182
 
@@ -218,20 +219,20 @@ def read_srml_month_from_solardat(station, year, month, filetype='PO',
218
219
  References
219
220
  ----------
220
221
  .. [1] University of Oregon Solar Radiation Measurement Laboratory
221
- `http://solardat.uoregon.edu/ <http://solardat.uoregon.edu/>`_
222
+ http://solardata.uoregon.edu/
222
223
  """
223
224
  file_name = "{station}{filetype}{year:02d}{month:02d}.txt".format(
224
225
  station=station,
225
226
  filetype=filetype,
226
227
  year=year % 100,
227
228
  month=month)
228
- url = "http://solardat.uoregon.edu/download/Archive/"
229
+ url = "http://solardata.uoregon.edu/download/Archive/"
229
230
  data = read_srml(url + file_name, map_variables=map_variables)
230
231
  return data
231
232
 
232
233
 
233
234
  def get_srml(station, start, end, filetype='PO', map_variables=True,
234
- url="http://solardat.uoregon.edu/download/Archive/"):
235
+ url="http://solardata.uoregon.edu/download/Archive/"):
235
236
  """Request data from UoO SRML and read it into a Dataframe.
236
237
 
237
238
  The University of Oregon Solar Radiation Monitoring Laboratory (SRML) is
@@ -252,7 +253,7 @@ def get_srml(station, start, end, filetype='PO', map_variables=True,
252
253
  map_variables : bool, default: True
253
254
  When true, renames columns of the DataFrame to pvlib variable names
254
255
  where applicable. See variable :const:`VARIABLE_MAP`.
255
- url : str, default: 'http://solardat.uoregon.edu/download/Archive/'
256
+ url : str, default: 'http://solardata.uoregon.edu/download/Archive/'
256
257
  API endpoint URL
257
258
 
258
259
  Returns
@@ -287,10 +288,9 @@ def get_srml(station, start, end, filetype='PO', map_variables=True,
287
288
  References
288
289
  ----------
289
290
  .. [1] University of Oregon Solar Radiation Measurement Laboratory
290
- `http://solardat.uoregon.edu/ <http://solardat.uoregon.edu/>`_
291
+ http://solardata.uoregon.edu/
291
292
  .. [2] Station ID codes - Solar Radiation Measurement Laboratory
292
- `http://solardat.uoregon.edu/StationIDCodes.html
293
- <http://solardat.uoregon.edu/StationIDCodes.html>`_
293
+ http://solardata.uoregon.edu/StationIDCodes.html
294
294
  """
295
295
  # Use pd.to_datetime so that strings (e.g. '2021-01-01') are accepted
296
296
  start = pd.to_datetime(start)
pvlib/iotools/surfrad.py CHANGED
@@ -146,13 +146,13 @@ def read_surfrad(filename, map_variables=True):
146
146
  metadata['surfrad_version'] = int(metadata_list[-1])
147
147
  metadata['tz'] = 'UTC'
148
148
 
149
- data = pd.read_csv(file_buffer, delim_whitespace=True,
149
+ data = pd.read_csv(file_buffer, sep=r'\s+',
150
150
  header=None, names=SURFRAD_COLUMNS)
151
151
  file_buffer.close()
152
152
 
153
153
  data = _format_index(data)
154
154
  missing = data == -9999.9
155
- data = data.where(~missing, np.NaN)
155
+ data = data.where(~missing, np.nan)
156
156
 
157
157
  if map_variables:
158
158
  data.rename(columns=VARIABLE_MAP, inplace=True)