Skip to content

sunrise and sunset from pyephem #588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
90ea2e0
Initial commit for ephem_next_rise_set
cwhanse Sep 21, 2018
0b9df18
Stickler issues
cwhanse Sep 21, 2018
86cdfa7
Change test result and rounding to nearest minute
cwhanse Sep 21, 2018
06aef3f
Edit test condition, round not floor
cwhanse Sep 21, 2018
a22d846
Change explicit rounding to pandas .round('min')
cwhanse Sep 22, 2018
719009d
More test fixes
cwhanse Sep 22, 2018
aa4f1d6
Fix sec keyword
cwhanse Sep 22, 2018
c08d213
Add test for transit
cwhanse Sep 23, 2018
f192547
Fix del statement
cwhanse Sep 23, 2018
377658b
Fix column order
cwhanse Sep 23, 2018
f338781
Change reference solution to NREL SPA website
cwhanse Sep 23, 2018
a3bedc0
Adjust pressure and temp for pyephem
cwhanse Sep 23, 2018
17564dd
Add horizon kwarg for pyephem
cwhanse Sep 23, 2018
c29ed06
Separate rise, set test for spa and pyephem
cwhanse Sep 23, 2018
a3d143d
Add horizon kwarg to pyephem, fix test condition for spa
cwhanse Sep 23, 2018
99a75a0
Add horizon kwarg to calc_time
cwhanse Sep 23, 2018
95187d5
Extend tests for next_rise_set_ephem
cwhanse Sep 24, 2018
e318bad
Remove dtype from expected...spa
cwhanse Sep 24, 2018
a88a5b6
Add previous rise/set capability
cwhanse Sep 28, 2018
2121aa0
style fixes
cwhanse Sep 28, 2018
c75b10e
Fix expected result
cwhanse Sep 28, 2018
f85a46a
Fix timezone test
cwhanse Sep 29, 2018
6238321
Documentation updates
cwhanse Sep 29, 2018
3618022
Fix spacing
cwhanse Sep 29, 2018
36bf3cc
Add transit, change golden to fixture, add horizon kwarg and error tests
cwhanse Sep 30, 2018
39956cc
style fixes
cwhanse Sep 30, 2018
c0ae1c5
Merge branch 'master' into ephem_rise_set
cwhanse Sep 30, 2018
f615f12
Fix references to fixture golden_mst, golden
cwhanse Sep 30, 2018
47c95c5
Add transit output to rise_set_transit_ephem
cwhanse Sep 30, 2018
bef44f6
Fix transit conflict
cwhanse Sep 30, 2018
c9b5348
Fix column order
cwhanse Sep 30, 2018
92e0677
Minor test adjustments
cwhanse Sep 30, 2018
db08f8d
Add handling of utcoffset
cwhanse Oct 1, 2018
c05bae7
Inline comment
cwhanse Oct 1, 2018
da2a0e2
Merge branch 'master' into ephem_rise_set
wholmgren Oct 4, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion pvlib/solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,24 @@ def get_sun_rise_set_transit(time, latitude, longitude, how='numpy',
return result


def _ephem_convert_to_seconds_and_microseconds(date):
# utility from unreleased PyEphem 3.6.7.1
"""Converts a PyEphem date into seconds"""
microseconds = int(round(24 * 60 * 60 * 1000000 * date))
seconds, microseconds = divmod(microseconds, 1000000)
seconds -= 2209032000 # difference between epoch 1900 and epoch 1970
return seconds, microseconds


def _ephem_to_timezone(date, tzinfo):
# utility from unreleased PyEphem 3.6.7.1
""""Convert a PyEphem Date into a timezone aware python datetime"""
seconds, microseconds = _ephem_convert_to_seconds_and_microseconds(date)
date = dt.datetime.fromtimestamp(seconds, tzinfo)
date = date.replace(microsecond=microseconds)
return date


def _ephem_setup(latitude, longitude, altitude, pressure, temperature):
import ephem
# initialize a PyEphem observer
Expand All @@ -453,6 +471,63 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature):
return obs, sun


def ephem_next_rise_set(time, latitude, longitude, altitude=0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would next_sun_rise_set_pyephem be more consistent with your preferred naming scheme?

Copy link
Member Author

@cwhanse cwhanse Sep 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll change. I have in mind Location.get_next_sunrise, Location.get_previous_sunrise as wrapper methods.

pressure=101325, temperature=12):
"""
Calculate the next sunrise and sunset times using the PyEphem package.

Parameters
----------
time : pandas.DatetimeIndex
Localized or UTC.
latitude : float
positive is north of 0
longitude : float
positive is east of 0
altitude : float, default 0
distance above sea level in meters.
pressure : int or float, optional, default 101325
air pressure in Pascals.
temperature : int or float, optional, default 12
air temperature in degrees C.

Returns
-------
pandas.DataFrame
index is the same as input time.index
columns are 'sunrise' and 'sunset', times are localized to the
timezone time.tz

See also
--------
pyephem
"""

try:
import ephem
except ImportError:
raise ImportError('PyEphem must be installed')

# if localized, convert to UTC. otherwise, assume UTC.
try:
time_utc = time.tz_convert('UTC')
except TypeError:
time_utc = time

obs, sun = _ephem_setup(latitude, longitude, altitude,
pressure, temperature)
# create lists of the next sunrise and sunset time localized to time.tz
next_sunrise = []
next_sunset = []
for thetime in time_utc:
obs.date = ephem.Date(thetime)
next_sunrise.append(_ephem_to_timezone(obs.next_rising(sun), time.tz))
next_sunset.append(_ephem_to_timezone(obs.next_setting(sun), time.tz))

return pd.DataFrame(index=time, data={'sunrise': next_sunrise,
'sunset': next_sunset})


def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
temperature=12):
"""
Expand All @@ -463,9 +538,11 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
time : pandas.DatetimeIndex
Localized or UTC.
latitude : float
positive is north of 0
longitude : float
positive is east of 0
altitude : float, default 0
distance above sea level.
distance above sea level in meters.
pressure : int or float, optional, default 101325
air pressure in Pascals.
temperature : int or float, optional, default 12
Expand Down
79 changes: 54 additions & 25 deletions pvlib/test/test_solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

tol = 5


@pytest.fixture()
def expected_solpos():
return pd.DataFrame({'elevation': 39.872046,
Expand All @@ -37,6 +38,7 @@ def expected_solpos():
'apparent_elevation': 39.888378},
index=['2003-10-17T12:30:30Z'])


@pytest.fixture()
def expected_solpos_multi():
return pd.DataFrame({'elevation': [39.872046, 39.505196],
Expand All @@ -45,6 +47,27 @@ def expected_solpos_multi():
'apparent_elevation': [39.888378, 39.521740]},
index=[['2003-10-17T12:30:30Z', '2003-10-18T12:30:30Z']])


@pytest.fixture()
def expected_rise_set():
# for Golden, CO, from USNO website
times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2),
datetime.datetime(2015, 8, 2),
]).tz_localize('MST')
sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 22, 0),
datetime.datetime(2015, 8, 2, 5, 0, 0)
]).tz_localize('MST').tolist()
sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0),
datetime.datetime(2015, 8, 2, 19, 13, 0)
]).tz_localize('MST').tolist()
transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0),
datetime.datetime(2015, 8, 2, 12, 7, 0)
]).tz_localize('MST').tolist()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E124 closing bracket does not match visual indentation

return pd.DataFrame({'sunrise': sunrise,
'sunset': sunset,
'transit': transit}, index=times)


# the physical tests are run at the same time as the NREL SPA test.
# pyephem reproduces the NREL result to 2 decimal places.
# this doesn't mean that one code is better than the other.
Expand Down Expand Up @@ -125,7 +148,7 @@ def test_spa_python_numba_physical_dst(expected_solpos):


@needs_pandas_0_17
def test_get_sun_rise_set_transit():
def test_get_sun_rise_set_transit(expected_rise_set):
south = Location(-35.0, 0.0, tz='UTC')
times = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 0),
datetime.datetime(2004, 12, 4, 0)]
Expand All @@ -136,47 +159,50 @@ def test_get_sun_rise_set_transit():
sunset = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 17, 1, 4),
datetime.datetime(2004, 12, 4, 19, 2, 2)]
).tz_localize('UTC').tolist()
frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times)

result = solarposition.get_sun_rise_set_transit(times, south.latitude,
south.longitude,
delta_t=64.0)
frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times)
result_rounded = pd.DataFrame(index=result.index)
# need to iterate because to_datetime does not accept 2D data
# the rounding fails on pandas < 0.17
for col, data in result.iteritems():
result_rounded[col] = pd.to_datetime(
np.floor(data.values.astype(np.int64) / 1e9)*1e9, utc=True)
result_rounded[col] = data.dt.round('1s')

del result_rounded['transit']
assert_frame_equal(frame, result_rounded)


# tests from USNO
# Golden
golden = Location(39.0, -105.0, tz='MST')
times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2),
datetime.datetime(2015, 8, 2),]
).tz_localize('MST')
sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 19, 2),
datetime.datetime(2015, 8, 2, 5, 1, 26)
]).tz_localize('MST').tolist()
sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 49, 10),
datetime.datetime(2015, 8, 2, 19, 11, 31)
]).tz_localize('MST').tolist()
result = solarposition.get_sun_rise_set_transit(times, golden.latitude,
# test for Golden, CO compare to USNO
result = solarposition.get_sun_rise_set_transit(expected_rise_set.index,
golden.latitude,
golden.longitude,
delta_t=64.0)
frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times)

# round to nearest minute
result_rounded = pd.DataFrame(index=result.index)
# need to iterate because to_datetime does not accept 2D data
# the rounding fails on pandas < 0.17
for col, data in result.iteritems():
result_rounded[col] = (pd.to_datetime(
np.floor(data.values.astype(np.int64) / 1e9)*1e9, utc=True)
.tz_convert('MST'))
result_rounded[col] = data.dt.round('min').tz_convert('MST')

del result_rounded['transit']
assert_frame_equal(frame, result_rounded)
assert_frame_equal(expected_rise_set, result_rounded)


@requires_ephem
def test_next_rise_set_ephem(expected_rise_set):
# test for Golden, CO compare to USNO
result = solarposition.ephem_next_rise_set(expected_rise_set.index,
golden.latitude,
golden.longitude,
golden.altitude,
pressure=101325,
temperature=12)
# round to nearest minute
result_rounded = pd.DataFrame(index=result.index)
for col, data in result.iteritems():
result_rounded[col] = data.dt.round('min').tz_convert('MST')
del expected_rise_set('transit')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E999 SyntaxError: can't delete function call

assert_frame_equal(expected_rise_set, result_rounded)


@requires_ephem
Expand All @@ -190,6 +216,7 @@ def test_pyephem_physical(expected_solpos):
assert_frame_equal(expected_solpos.round(2),
ephem_data[expected_solpos.columns].round(2))


@requires_ephem
def test_pyephem_physical_dst(expected_solpos):
times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1,
Expand All @@ -201,6 +228,7 @@ def test_pyephem_physical_dst(expected_solpos):
assert_frame_equal(expected_solpos.round(2),
ephem_data[expected_solpos.columns].round(2))


@requires_ephem
def test_calc_time():
import pytz
Expand All @@ -227,6 +255,7 @@ def test_calc_time():
assert_allclose((az.replace(second=0, microsecond=0) -
epoch_dt).total_seconds(), actual_timestamp)


@requires_ephem
def test_earthsun_distance():
times = pd.date_range(datetime.datetime(2003,10,17,13,30,30),
Expand Down