@@ -219,39 +219,27 @@ def ephemeris(time, location, pressure=101325, temperature=12):
219
219
----------
220
220
time : pandas.DatetimeIndex
221
221
location : pvlib.Location
222
-
223
- Other Parameters
224
- ----------------
225
-
226
- pressure : float or DataFrame
227
- Ambient pressure (Pascals)
228
-
229
- temperature : float or DataFrame
230
- Ambient temperature (C)
222
+ pressure : float or Series
223
+ Ambient pressure (Pascals)
224
+ temperature : float or Series
225
+ Ambient temperature (C)
231
226
232
227
Returns
233
228
-------
234
229
235
- DataFrame with the following columns
236
-
237
- SunEl : float of DataFrame
238
- Actual elevation (not accounting for refraction)of the sun
239
- in decimal degrees, 0 = on horizon. The complement of the True Zenith
240
- Angle.
241
-
242
- SunAz : Azimuth of the sun in decimal degrees from North.
243
- 0 = North, 90 = West, 180 = South, 270 = West
244
-
245
- SunZen : Solar zenith angle
246
-
247
- ApparentSunEl : float or DataFrame
248
-
249
- Apparent sun elevation accounting for atmospheric
250
- refraction. This is the complement of the Apparent Zenith Angle.
251
-
252
- SolarTime : fload or DataFrame
253
- Solar time in decimal hours (solar noon is 12.00).
230
+ DataFrame with the following columns:
254
231
232
+ * apparent_elevation : apparent sun elevation accounting for
233
+ atmospheric refraction.
234
+ * elevation : actual elevation (not accounting for refraction)
235
+ of the sun in decimal degrees, 0 = on horizon.
236
+ The complement of the zenith angle.
237
+ * azimuth : Azimuth of the sun in decimal degrees East of North.
238
+ This is the complement of the apparent zenith angle.
239
+ * apparent_zenith : apparent sun zenith accounting for atmospheric
240
+ refraction.
241
+ * zenith : Solar zenith angle
242
+ * solar_time : Solar time in decimal hours (solar noon is 12.00).
255
243
256
244
References
257
245
-----------
@@ -262,133 +250,129 @@ def ephemeris(time, location, pressure=101325, temperature=12):
262
250
See also
263
251
--------
264
252
pyephem, spa
265
-
266
253
'''
267
254
268
255
# Added by Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
269
256
# Edited by Will Holmgren (@wholmgren), University of Arizona, 2014
270
-
271
- pvl_logger .warning ('Using solarposition.ephemeris is discouraged. '
272
- 'solarposition.pyephem and solarposition.spa are '
273
- 'faster and more accurate.' )
257
+
258
+ # Most comments in this function are from PVLIB_MATLAB or from
259
+ # pvlib-python's attempt to understand and fix problems with the
260
+ # algorithm. The comments are *not* based on the reference material.
261
+ # This helps a little bit:
262
+ # http://www.cv.nrao.edu/~rfisher/Ephemerides/times.html
274
263
275
264
pvl_logger .debug ('location={}, temperature={}, pressure={}' .format (
276
265
location , temperature , pressure ))
277
266
267
+ # the inversion of longitude is due to the fact that this code was
268
+ # originally written for the convention that positive longitude were for
269
+ # locations west of the prime meridian. However, the correct convention (as
270
+ # of 2009) is to use negative longitudes for locations west of the prime
271
+ # meridian. Therefore, the user should input longitude values under the
272
+ # correct convention (e.g. Albuquerque is at -106 longitude), but it needs
273
+ # to be inverted for use in the code.
274
+
278
275
Latitude = location .latitude
279
- ''' the inversion of longitude is due to the fact that this code was
280
- originally written for the convention that positive longitude were for
281
- locations west of the prime meridian. However, the correct convention (as
282
- of 2009) is to use negative longitudes for locations west of the prime
283
- meridian. Therefore, the user should input longitude values under the
284
- correct convention (e.g. Albuquerque is at -106 longitude), but it needs
285
- to be inverted for use in the code.
286
- '''
287
- Latitude = location .latitude
288
- Longitude = 1 * location .longitude
289
- Year = time .year
290
- Hour = time .hour
291
- Minute = time .minute
292
- Second = time .second
293
- DayOfYear = time .dayofyear
294
-
295
- DecHours = Hour + Minute / float (60 ) + Second / float (3600 )
296
-
297
- Abber = 20 / float (3600 )
276
+ Longitude = - 1 * location .longitude
277
+
278
+ Abber = 20 / 3600.
298
279
LatR = np .radians (Latitude )
299
-
300
- # this code is needed to handle the new location.tz format string
301
- try :
302
- utc_offset = pytz .timezone (location .tz ).utcoffset (
303
- time [0 ]).total_seconds () / 3600.
304
- except ValueError :
305
- utc_offset = time .tzinfo .utcoffset (time [0 ]).total_seconds () / 3600.
306
- pvl_logger .debug ('utc_offset={}' .format (utc_offset ))
280
+
281
+ # the SPA algorithm needs time to be expressed in terms of
282
+ # decimal UTC hours of the day of the year.
283
+
284
+ # first convert to utc
285
+ time_utc = localize_to_utc (time , location )
286
+
287
+ # strip out the day of the year and calculate the decimal hour
288
+ DayOfYear = time_utc .dayofyear
289
+ DecHours = (time_utc .hour + time_utc .minute / 60. + time_utc .second / 3600. +
290
+ time_utc .microsecond / 3600.e6 )
307
291
308
292
UnivDate = DayOfYear
293
+ UnivHr = DecHours
309
294
310
- # ToDo: Will H: surprised to see the 0.5 here, but moving on...
311
- UnivHr = DecHours + utc_offset - .5
312
-
313
- Yr = Year - 1900
314
-
315
- YrBegin = 365 * Yr + np .floor ((Yr - 1 ) / float (4 )) - 0.5
295
+ Yr = time_utc .year - 1900
296
+ YrBegin = 365 * Yr + np .floor ((Yr - 1 ) / 4. ) - 0.5
316
297
317
298
Ezero = YrBegin + UnivDate
318
- T = Ezero / float (36525 )
319
- GMST0 = 6 / float (24 ) + 38 / float (1440 ) + (
320
- 45.836 + 8640184.542 * T + 0.0929 * T ** 2 ) / float (86400 )
299
+ T = Ezero / 36525.
300
+
301
+ # Calculate Greenwich Mean Sidereal Time (GMST)
302
+ GMST0 = 6 / 24. + 38 / 1440. + (
303
+ 45.836 + 8640184.542 * T + 0.0929 * T ** 2 ) / 86400.
321
304
GMST0 = 360 * (GMST0 - np .floor (GMST0 ))
322
- GMSTi = np .mod (GMST0 + 360 * (1.0027379093 * UnivHr / float (24 )), 360 )
323
-
305
+ GMSTi = np .mod (GMST0 + 360 * (1.0027379093 * UnivHr / 24. ), 360 )
306
+
307
+ # Local apparent sidereal time
324
308
LocAST = np .mod ((360 + GMSTi - Longitude ), 360 )
325
- EpochDate = Ezero + UnivHr / float (24 )
326
- T1 = EpochDate / float (36525 )
309
+
310
+ EpochDate = Ezero + UnivHr / 24.
311
+ T1 = EpochDate / 36525.
312
+
327
313
ObliquityR = np .radians (
328
314
23.452294 - 0.0130125 * T1 - 1.64e-06 * T1 ** 2 + 5.03e-07 * T1 ** 3 )
329
315
MlPerigee = 281.22083 + 4.70684e-05 * EpochDate + 0.000453 * T1 ** 2 + (
330
316
3e-06 * T1 ** 3 )
331
317
MeanAnom = np .mod ((358.47583 + 0.985600267 * EpochDate - 0.00015 *
332
- T1 ** 2 - 3e-06 * T1 ** 3 ), 360 )
318
+ T1 ** 2 - 3e-06 * T1 ** 3 ), 360 )
333
319
Eccen = 0.01675104 - 4.18e-05 * T1 - 1.26e-07 * T1 ** 2
334
320
EccenAnom = MeanAnom
335
321
E = 0
336
322
337
323
while np .max (abs (EccenAnom - E )) > 0.0001 :
338
324
E = EccenAnom
339
- EccenAnom = MeanAnom + np .degrees (Eccen ) * ( np .sin (np .radians (E ) ))
325
+ EccenAnom = MeanAnom + np .degrees (Eccen )* np .sin (np .radians (E ))
340
326
341
- # pdb.set_trace()
342
327
TrueAnom = (
343
328
2 * np .mod (np .degrees (np .arctan2 (((1 + Eccen ) / (1 - Eccen )) ** 0.5 *
344
- np .tan (np .radians (EccenAnom ) / float ( 2 ) ), 1 )), 360 ))
329
+ np .tan (np .radians (EccenAnom ) / 2. ), 1 )), 360 ))
345
330
EcLon = np .mod (MlPerigee + TrueAnom , 360 ) - Abber
346
331
EcLonR = np .radians (EcLon )
347
- DecR = np .arcsin (np .sin (ObliquityR )* ( np .sin (EcLonR ) ))
332
+ DecR = np .arcsin (np .sin (ObliquityR )* np .sin (EcLonR ))
348
333
349
- RtAscen = np .degrees (np .arctan2 (np .cos (ObliquityR ) * (( np .sin (EcLonR )) ),
350
- np .cos (EcLonR )))
334
+ RtAscen = np .degrees (np .arctan2 (np .cos (ObliquityR )* np .sin (EcLonR ),
335
+ np .cos (EcLonR ) ))
351
336
352
337
HrAngle = LocAST - RtAscen
353
338
HrAngleR = np .radians (HrAngle )
354
-
355
339
HrAngle = HrAngle - (360 * ((abs (HrAngle ) > 180 )))
356
- SunAz = np .degrees (np .arctan2 (- 1 * np .sin (HrAngleR ), np .cos (LatR ) *
357
- (np .tan (DecR )) - np .sin (LatR )* (np .cos (HrAngleR ))))
358
- SunAz = SunAz + (SunAz < 0 ) * 360
359
- # potential error in the following line
360
- SunEl = np .degrees (np .arcsin ((np .cos (LatR ) * (np .cos (DecR )) *
361
- (np .cos (HrAngleR )) + np .sin (LatR )* (np .sin (DecR )))))
362
- SolarTime = (180 + HrAngle ) / float (15 )
363
-
364
- Refract = []
365
-
366
- for Elevation in SunEl :
367
- TanEl = np .tan (np .radians (Elevation ))
368
- if Elevation > 5 and Elevation <= 85 :
369
- Refract .append ((58.1 / float (TanEl ) - 0.07 / float (TanEl ** 3 ) +
370
- 8.6e-05 / float (TanEl ** 5 )))
371
- elif Elevation > - 0.575 and Elevation <= 5 :
372
- Refract .append ((Elevation * ((- 518.2 + Elevation *
373
- ((103.4 + Elevation * ((- 12.79 + Elevation *
374
- (0.711 ))))))) + 1735 ))
375
- elif Elevation > - 1 and Elevation <= - 0.575 :
376
- Refract .append (- 20.774 / float (TanEl ))
377
- else :
378
- Refract .append (0 )
379
-
380
- Refract = (np .array (Refract ) * ((283 / float (273 + temperature ))) *
381
- pressure / float (101325 ) / float (3600 ))
382
-
383
- SunZen = 90 - SunEl
340
+
341
+ SunAz = np .degrees (np .arctan2 (- np .sin (HrAngleR ),
342
+ np .cos (LatR )* np .tan (DecR ) - np .sin (LatR )* np .cos (HrAngleR ) ))
343
+ SunAz [SunAz < 0 ] += 360
344
+
345
+ SunEl = np .degrees (np .arcsin (
346
+ np .cos (LatR ) * np .cos (DecR ) * np .cos (HrAngleR ) +
347
+ np .sin (LatR ) * np .sin (DecR ) ))
348
+
349
+ SolarTime = (180 + HrAngle ) / 15.
350
+
351
+ # Calculate refraction correction
352
+ Elevation = SunEl
353
+ TanEl = pd .Series (np .tan (np .radians (Elevation )), index = time_utc )
354
+ Refract = pd .Series (0 , index = time_utc )
355
+
356
+ Refract [(Elevation > 5 ) & (Elevation <= 85 )] = (
357
+ 58.1 / TanEl - 0.07 / (TanEl ** 3 ) + 8.6e-05 / (TanEl ** 5 ) )
358
+
359
+ Refract [(Elevation > - 0.575 ) & (Elevation <= 5 )] = ( Elevation *
360
+ (- 518.2 + Elevation * (103.4 + Elevation * (- 12.79 + Elevation * 0.711 ))) +
361
+ 1735 )
362
+
363
+ Refract [(Elevation > - 1 ) & (Elevation <= - 0.575 )] = - 20.774 / TanEl
364
+
365
+ Refract *= (283 / (273. + temperature )) * (pressure / 101325. ) / 3600.
384
366
385
367
ApparentSunEl = SunEl + Refract
386
368
387
- DFOut = pd .DataFrame ({'elevation' : SunEl }, index = time )
388
- DFOut ['azimuth' ] = SunAz
389
- DFOut ['zenith' ] = SunZen
369
+ # make output DataFrame
370
+ DFOut = pd .DataFrame (index = time_utc ).tz_convert (location .tz )
390
371
DFOut ['apparent_elevation' ] = ApparentSunEl
372
+ DFOut ['elevation' ] = SunEl
373
+ DFOut ['azimuth' ] = SunAz
391
374
DFOut ['apparent_zenith' ] = 90 - ApparentSunEl
375
+ DFOut ['zenith' ] = 90 - SunEl
392
376
DFOut ['solar_time' ] = SolarTime
393
377
394
378
return DFOut
0 commit comments