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.
- pvlib/__init__.py +1 -0
- pvlib/bifacial/utils.py +2 -1
- pvlib/clearsky.py +7 -8
- pvlib/iam.py +3 -3
- pvlib/inverter.py +3 -3
- pvlib/iotools/__init__.py +2 -0
- pvlib/iotools/solargis.py +214 -0
- pvlib/iotools/solcast.py +2 -7
- pvlib/iotools/solrad.py +121 -23
- pvlib/iotools/srml.py +12 -12
- pvlib/iotools/surfrad.py +2 -2
- pvlib/irradiance.py +28 -22
- pvlib/location.py +3 -1
- pvlib/modelchain.py +10 -9
- pvlib/pvarray.py +127 -0
- pvlib/pvsystem.py +52 -43
- pvlib/scaling.py +4 -2
- pvlib/shading.py +110 -0
- pvlib/singlediode.py +37 -9
- pvlib/snow.py +3 -1
- pvlib/solarposition.py +38 -30
- pvlib/spa.py +3 -11
- pvlib/spectrum/mismatch.py +2 -1
- pvlib/temperature.py +3 -2
- pvlib/tests/bifacial/test_utils.py +6 -5
- pvlib/tests/conftest.py +13 -14
- pvlib/tests/iotools/test_sodapro.py +2 -1
- pvlib/tests/iotools/test_solargis.py +68 -0
- pvlib/tests/iotools/test_solcast.py +2 -2
- pvlib/tests/iotools/test_solrad.py +58 -7
- pvlib/tests/iotools/test_srml.py +7 -14
- pvlib/tests/test_clearsky.py +1 -1
- pvlib/tests/test_irradiance.py +24 -8
- pvlib/tests/test_location.py +1 -1
- pvlib/tests/test_modelchain.py +37 -26
- pvlib/tests/test_pvarray.py +25 -0
- pvlib/tests/test_pvsystem.py +76 -104
- pvlib/tests/test_shading.py +130 -11
- pvlib/tests/test_singlediode.py +68 -10
- pvlib/tests/test_snow.py +1 -1
- pvlib/tests/test_solarposition.py +121 -7
- pvlib/tests/test_spa.py +5 -15
- pvlib/tests/test_temperature.py +4 -4
- pvlib/tests/test_tracking.py +2 -2
- pvlib/tracking.py +8 -38
- pvlib/version.py +1 -5
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/METADATA +9 -33
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/RECORD +52 -51
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/WHEEL +1 -1
- pvlib/spa_c_files/SPA_NOTICE.md +0 -39
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/LICENSE +0 -0
- {pvlib-0.10.3.dist-info → pvlib-0.10.5.dist-info}/top_level.txt +0 -0
pvlib/__init__.py
CHANGED
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
|
|
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
|
|
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]
|
|
84
|
-
|
|
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 <
|
|
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
|
|
73
|
-
https://
|
|
74
|
-
|
|
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']
|
|
536
|
-
coeffs['p_dc']
|
|
537
|
-
coeffs['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
|
|
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(
|
|
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.
|
|
53
|
-
|
|
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(
|
|
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://
|
|
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
|
-
|
|
63
|
+
http://solardata.uoregon.edu/
|
|
64
64
|
.. [2] `Archival (short interval) data files
|
|
65
|
-
<http://
|
|
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.
|
|
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
|
-
"""
|
|
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
|
-
|
|
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://
|
|
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://
|
|
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://
|
|
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
|
-
|
|
291
|
+
http://solardata.uoregon.edu/
|
|
291
292
|
.. [2] Station ID codes - Solar Radiation Measurement Laboratory
|
|
292
|
-
|
|
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,
|
|
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.
|
|
155
|
+
data = data.where(~missing, np.nan)
|
|
156
156
|
|
|
157
157
|
if map_variables:
|
|
158
158
|
data.rename(columns=VARIABLE_MAP, inplace=True)
|