1
+ __author__ = 'Jan Pecinovsky'
2
+
3
+ import geocoder
4
+ import astral
5
+ import math
6
+ import pandas as pd
7
+
8
+ class SolarInsolation (object ):
9
+ """
10
+ Module to calculate Solar Insolation (direct intensity, global intensity, air mass) and
11
+ basic solar parameters (angle) based on a location and a date.
12
+ Formulas from pveducation.org
13
+ """
14
+ def __init__ (self , location ):
15
+ """
16
+ Parameters
17
+ ----------
18
+ location: String
19
+ """
20
+ self .location = geocoder .google (location )
21
+ self .elevation = geocoder .google (self .location .latlng , method = 'Elevation' ).elevation
22
+ self .astral = astral .Astral ()
23
+
24
+ def _airMass (self , angleFromVertical ):
25
+ """
26
+ Raw formula to calculate air mass
27
+
28
+ Parameters
29
+ ----------
30
+ angleFromVertical: float
31
+ in radians
32
+
33
+ Returns
34
+ -------
35
+ float
36
+ """
37
+ denom = math .cos (angleFromVertical ) + 0.50572 * (96.07995 - angleFromVertical ) ** - 1.6364
38
+ if denom >= 0 :
39
+ return 1 / denom
40
+ else :
41
+ return - 1
42
+
43
+ def airMass (self , datetime ):
44
+ """
45
+ Calculate air mass for a given date and time
46
+
47
+ Parameters
48
+ ----------
49
+ datetime: datetime.datetime
50
+
51
+ Returns
52
+ -------
53
+ float
54
+ """
55
+ angleFromVertical = (math .pi / 2 ) - self .solarElevation (datetime )
56
+ return self ._airMass (angleFromVertical )
57
+
58
+ def _directIntensity (self , elevation , airMass ):
59
+ """
60
+ Raw Formula to calculate direct solar beam intensity
61
+
62
+ Parameters
63
+ ---------
64
+ elevation: float
65
+ in meters
66
+ airMass: float
67
+
68
+ Returns
69
+ -------
70
+ float
71
+ in W/m**2
72
+ """
73
+ elevation = elevation / 1000 #formula uses km
74
+ di = 1.353 * ((1 - 0.14 * elevation ) * 0.7 ** airMass ** 0.678 + 0.14 * elevation )
75
+ return di * 1000 #formula output is kW/m**2
76
+
77
+ def directIntensity (self , datetime ):
78
+ """
79
+ Calculate direct solar beam intensity for a given date and time
80
+
81
+ Parameters
82
+ ----------
83
+ datetime: datetime.datetime
84
+
85
+ Returns
86
+ -------
87
+ float
88
+ in W/m**2
89
+ """
90
+ airMass = self .airMass (datetime )
91
+ if airMass == - 1 :
92
+ return 0
93
+ return self ._directIntensity (self .elevation , airMass )
94
+
95
+ def _backgroundIrradiance (self , directIntensity ):
96
+ """
97
+ Calculate the background irradiance, which is 10% of the direct intensity
98
+ :param directIntensity: float
99
+ :return: float
100
+ """
101
+
102
+ return 0.1 * directIntensity
103
+
104
+ def _globalIrradiance (self , directIntensity ):
105
+ """
106
+ Raw formula to calculate global solar irradiance
107
+
108
+ Parameters
109
+ ----------
110
+ directIntensity: float
111
+ in W/m**2
112
+
113
+ Returns
114
+ -------
115
+ float
116
+ in W/m**2
117
+ """
118
+ return directIntensity + self ._backgroundIrradiance (directIntensity )
119
+
120
+ def globalIrradiance (self , datetime ):
121
+ """
122
+ Calculate global solar irradiance for a given date and time
123
+
124
+ Parameters
125
+ ----------
126
+ datetime: datetime.datetime
127
+
128
+ Returns
129
+ -------
130
+ float
131
+ in W/m**2
132
+ """
133
+ directIntensity = self .directIntensity (datetime )
134
+ return self ._globalIrradiance (directIntensity )
135
+
136
+ def solarElevation (self , datetime ):
137
+ """
138
+ Calculate angle of the sun above the horizon
139
+
140
+ Parameters
141
+ ----------
142
+ datetime: datetime.datetime
143
+
144
+ Returns
145
+ -------
146
+ float
147
+ in radians
148
+ """
149
+ deg = self .astral .solar_elevation (dateandtime = datetime ,
150
+ latitude = self .location .lat ,
151
+ longitude = self .location .lng )
152
+ return math .radians (deg )
153
+
154
+ def df (self , start , end ):
155
+ """
156
+ Creates a dataframe with the insolation in W/m**2 in hourly resolution
157
+
158
+ Parameters
159
+ ----------
160
+ start, end: datetime.datetime
161
+
162
+ Returns
163
+ -------
164
+ Pandas Dataframe
165
+ """
166
+
167
+ hours = pd .date_range (start = start , end = end , freq = 'h' )
168
+ gis = []
169
+ for hour in hours :
170
+ gis .append (self .globalIrradiance (hour ))
171
+
172
+ df = pd .DataFrame (gis , index = hours , columns = ['insolation' ])
173
+ return df .tz_localize ('UTC' )
174
+
175
+ def solarAzimuth (self , datetime ):
176
+ """
177
+ Calculate the azimuth of the sun
178
+
179
+ :param datetime: datetime.datetime
180
+ :return: float
181
+ in radians
182
+ """
183
+
184
+ deg = self .astral .solar_azimuth (dateandtime = datetime ,
185
+ latitude = self .location .lat ,
186
+ longitude = self .location .lng )
187
+ return math .radians (deg )
188
+
189
+ class PVModel (SolarInsolation ):
190
+ """
191
+ Module that models a theoretically perfect PV installation,
192
+ extending the Solar Insolation Model, but adding PV orientation and tilt.
193
+ """
194
+
195
+ def __init__ (self , location , orient = 180 , tilt = 35 ):
196
+ """
197
+ Parameters
198
+ ----------
199
+ location: String
200
+ orient: number (optional, default=180 (south))
201
+ degrees (0-360)
202
+ tilt: number (optional, default=35)
203
+ degrees
204
+ """
205
+
206
+ super (PVModel , self ).__init__ (location = location )
207
+ self .orient = math .radians (orient )
208
+ self .tilt = math .radians (tilt )
209
+
210
+ def directIntensity (self , datetime ):
211
+ """
212
+ Calculate the direct solar beam intensity for a given date and time,
213
+ but compensate for the PV orientation and tilt
214
+
215
+ Parameters
216
+ ----------
217
+ datetime: datetime.datetime
218
+
219
+ Returns
220
+ -------
221
+ float
222
+ in W/m**2
223
+ """
224
+ di = super (PVModel , self ).directIntensity (datetime )
225
+ a = self .solarElevation (datetime )
226
+ b = self .tilt
227
+ c = self .orient
228
+ d = self .solarAzimuth (datetime )
229
+
230
+ PVdi = di * (math .cos (a )* math .sin (b )* math .cos (c - d ) + math .sin (a )* math .cos (b ))
231
+
232
+ #PVdi cannot be negative
233
+ return max (0 , PVdi )
234
+
235
+ def globalIrradiance (self , datetime ):
236
+ """
237
+ Calculate global solar irradiance for a given date and time
238
+ needed to override, because background irradiance is not influenced by PV orientation and tilt
239
+
240
+ Parameters
241
+ ----------
242
+ datetime: datetime.datetime
243
+
244
+ Returns
245
+ -------
246
+ float
247
+ in W/m**2
248
+ """
249
+ #calculate the direct intensity without influence of tilt and orientation
250
+ di = super (PVModel , self ).directIntensity (datetime )
251
+
252
+ #add the tilted and oriented direct intensity to the background irradiance
253
+ return self .directIntensity (datetime ) + self ._backgroundIrradiance (di )
0 commit comments