A fast, vectorized Python port of suncalc.js
Project description
suncalc-py
A fast, vectorized Python implementation of suncalc.js
for
calculating sun position and sunlight phases (times for sunrise, sunset, dusk,
etc.) for the given location and time.
While other similar libraries exist, I didn't originally encounter any that met my requirements of being both openly-licensed and vectorized 1
Install
pip install suncalc
Using
Example
suncalc
is designed to work both with single values and with arrays of values.
First, import the module:
from suncalc import get_position, get_times
from datetime import datetime
There are currently two methods: get_position
, to get the sun azimuth and
altitude for a given date and position, and get_times
, to get sunlight phases
for a given date and position.
date = datetime.now()
lon = 20
lat = 45
get_position(date, lon, lat)
# {'azimuth': -0.8619668996997687, 'altitude': 0.5586446727994595}
get_times(date, lon, lat)
# {'solar_noon': Timestamp('2020-11-20 08:47:08.410863770'),
# 'nadir': Timestamp('2020-11-19 20:47:08.410863770'),
# 'sunrise': Timestamp('2020-11-20 03:13:22.645455322'),
# 'sunset': Timestamp('2020-11-20 14:20:54.176272461'),
# 'sunrise_end': Timestamp('2020-11-20 03:15:48.318936035'),
# 'sunset_start': Timestamp('2020-11-20 14:18:28.502791748'),
# 'dawn': Timestamp('2020-11-20 02:50:00.045539551'),
# 'dusk': Timestamp('2020-11-20 14:44:16.776188232'),
# 'nautical_dawn': Timestamp('2020-11-20 02:23:10.019832520'),
# 'nautical_dusk': Timestamp('2020-11-20 15:11:06.801895264'),
# 'night_end': Timestamp('2020-11-20 01:56:36.144269287'),
# 'night': Timestamp('2020-11-20 15:37:40.677458252'),
# 'golden_hour_end': Timestamp('2020-11-20 03:44:46.795967773'),
# 'golden_hour': Timestamp('2020-11-20 13:49:30.025760010')}
These methods also work for arrays of data, and since the implementation is vectorized it's much faster than a for loop in Python.
import pandas as pd
df = pd.DataFrame({
'date': [date] * 10,
'lon': [lon] * 10,
'lat': [lat] * 10
})
pd.DataFrame(get_position(df['date'], df['lon'], df['lat']))
# azimuth altitude
# 0 -1.485509 -1.048223
# 1 -1.485509 -1.048223
# ...
pd.DataFrame(get_times(df['date'], df['lon'], df['lat']))['solar_noon']
# 0 2020-11-20 08:47:08.410863872+00:00
# 1 2020-11-20 08:47:08.410863872+00:00
# ...
# Name: solar_noon, dtype: datetime64[ns, UTC]
If you want to join this data back to your DataFrame
, you can use pd.concat
:
times = pd.DataFrame(get_times(df['date'], df['lon'], df['lat']))['solar_noon']
pd.concat([df, times], axis=1)
API
get_position
Calculate sun position for a given date and latitude/longitude
date
(datetime
or a pandas series of datetimes): date and time to find sun position oflng
(float
or numpy array offloat
): longitude to find sun position oflat
(float
or numpy array offloat
): latitude to find sun position of
get_times
-
date
(datetime
or a pandas series of datetimes): date and time to find sunlight phases of -
lng
(float
or numpy array offloat
): longitude to find sunlight phases of -
lat
(float
or numpy array offloat
): latitude to find sunlight phases of -
height
(float
or numpy array offloat
, default0
): observer height in meters -
times
(Iterable[Tuple[float, str, str]]
): an iterable defining the angle above the horizon and strings for custom sunlight phases. The default is:# (angle, morning name, evening name) DEFAULT_TIMES = [ (-0.833, 'sunrise', 'sunset'), (-0.3, 'sunrise_end', 'sunset_start'), (-6, 'dawn', 'dusk'), (-12, 'nautical_dawn', 'nautical_dusk'), (-18, 'night_end', 'night'), (6, 'golden_hour_end', 'golden_hour') ]
Benchmark
This benchmark is to show that the vectorized implementation is nearly 100x faster than a for loop in Python.
First set up a DataFrame
with random data. Here I create 100,000 rows.
from suncalc import get_position, get_times
import pandas as pd
def random_dates(start, end, n=10):
"""Create an array of random dates"""
start_u = start.value//10**9
end_u = end.value//10**9
return pd.to_datetime(np.random.randint(start_u, end_u, n), unit='s')
start = pd.to_datetime('2015-01-01')
end = pd.to_datetime('2018-01-01')
dates = random_dates(start, end, n=100_000)
lons = np.random.uniform(low=-179, high=179, size=(100_000,))
lats = np.random.uniform(low=-89, high=89, size=(100_000,))
df = pd.DataFrame({'date': dates, 'lat': lats, 'lon': lons})
Then compute SunCalc.get_position
two ways: the first using the vectorized
implementation and the second using df.apply
, which is equivalent to a for
loop. The first is more than 100x faster than the second.
%timeit get_position(df['date'], df['lon'], df['lat'])
# 41.4 ms ± 437 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit df.apply(lambda row: get_position(row['date'], row['lon'], row['lat']), axis=1)
# 4.89 s ± 184 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Likewise, compute SunCalc.get_times
the same two ways: first using the
vectorized implementation and the second using df.apply
. The first is 2800x
faster than the second!
%timeit get_times(df['date'], df['lon'], df['lat'])
# 55.3 ms ± 1.91 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%time df.apply(lambda row: get_times(row['date'], row['lon'], row['lat']), axis=1)
# CPU times: user 2min 33s, sys: 288 ms, total: 2min 34s
# Wall time: 2min 34s
1: pyorbital
looks great but is
GPL3-licensed; pysolar
is also
GPL3-licensed. pyEphem
is LGPL3-licensed. I
recently discovered sunpy
and
astropy
, both of which probably would've
worked but I didn't see them at first...
Changelog
[0.1.0] - 2020-11-19
- Initial release on PyPI
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file suncalc-0.1.0.tar.gz
.
File metadata
- Download URL: suncalc-0.1.0.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/50.3.2 requests-toolbelt/0.9.1 tqdm/4.48.2 CPython/3.7.7
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 33bb9f1b9d265c6c8f0b731be588b0ddcae825a7f50b98a80b11a74da7f8aac5 |
|
MD5 | 34969f39fb9eebe474ee15451f542fb2 |
|
BLAKE2b-256 | 23a5eccfe51a0562eb2d78d8cfca74d975fb184b0f9c9bda92805f8a59bca9ce |