Skip to main content

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 of
  • lng (float or numpy array of float): longitude to find sun position of
  • lat (float or numpy array of float): 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 of float): longitude to find sunlight phases of

  • lat (float or numpy array of float): latitude to find sunlight phases of

  • height (float or numpy array of float, default 0): 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

suncalc-0.1.0.tar.gz (9.7 kB view details)

Uploaded Source

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

Hashes for suncalc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 33bb9f1b9d265c6c8f0b731be588b0ddcae825a7f50b98a80b11a74da7f8aac5
MD5 34969f39fb9eebe474ee15451f542fb2
BLAKE2b-256 23a5eccfe51a0562eb2d78d8cfca74d975fb184b0f9c9bda92805f8a59bca9ce

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page