suncalc

A Python port of suncalc.js for calculating sun position and sunlight phases

https://github.com/kylebarron/suncalc-py

Science Score: 13.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.2%) to scientific vocabulary

Keywords

numpy sun sunrise sunset
Last synced: 6 months ago · JSON representation

Repository

A Python port of suncalc.js for calculating sun position and sunlight phases

Basic Info
  • Host: GitHub
  • Owner: kylebarron
  • License: mit
  • Language: Python
  • Default Branch: master
  • Homepage:
  • Size: 37.1 KB
Statistics
  • Stars: 67
  • Watchers: 3
  • Forks: 11
  • Open Issues: 9
  • Releases: 0
Topics
numpy sun sunrise sunset
Created over 5 years ago · Last pushed almost 2 years ago
Metadata Files
Readme Changelog License

README.md

suncalc-py

Test Package version Downloads

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 openly-licensed, vectorized, and simple to use 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:

py 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 (in radians) for a given date and position, and get_times, to get sunlight phases for a given date and position.

```py 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'),

'goldenhourend': 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.

```py 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(gettimes(df['date'], df['lon'], df['lat']))['solarnoon']

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:

py times = pd.DataFrame(get_times(df['date'], df['lon'], df['lat'])) pd.concat([df, times], axis=1)

API

get_position

Calculate sun position (azimuth and altitude) for a given date and latitude/longitude

  • date (datetime or a pandas series of datetimes): date and time to find sun position of. Datetime must be in UTC.
  • 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

Returns a dict with two keys: azimuth and altitude. If the input values were singletons, the dict's values will be floats. Otherwise they'll be numpy arrays of floats.

get_times

  • date (datetime or a pandas series of datetimes): date and time to find sunlight phases of. Datetime must be in UTC.
  • 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:

    ```py

    (angle, morning name, evening name)

    DEFAULTTIMES = [ (-0.833, 'sunrise', 'sunset'), (-0.3, 'sunriseend', 'sunsetstart'), (-6, 'dawn', 'dusk'), (-12, 'nauticaldawn', 'nauticaldusk'), (-18, 'nightend', 'night'), (6, 'goldenhourend', 'golden_hour') ] ```

Returns a dict where the keys are solar_noon, nadir, plus any keys passed in the times argument. If the input values were singletons, the dict's values will be of type datetime.datetime (or pd.Timestamp if you have pandas installed, which is a subclass of and therefore compatible with datetime.datetime). Otherwise they'll be pandas DateTime series. The returned times will be in UTC.

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.

```py from suncalc import getposition, gettimes import pandas as pd

def randomdates(start, end, n=10): """Create an array of random dates""" startu = start.value//109 end_u = end.value//109 return pd.todatetime(np.random.randint(startu, end_u, n), unit='s')

start = pd.todatetime('2015-01-01') end = pd.todatetime('2018-01-01') dates = randomdates(start, end, n=100000)

lons = np.random.uniform(low=-179, high=179, size=(100000,)) lats = np.random.uniform(low=-89, high=89, size=(100000,))

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.

```py %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! Some of the difference here is that under the hood the non-vectorized approach uses pd.to_datetime while the vectorized implementation uses np.astype('datetime64[ns, UTC]'). pd.to_datetime is really slow!!

```py %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. suncalcPy is another port of suncalc.js, and is MIT-licensed, but doesn't use Numpy and thus isn't vectorized. I recently discovered sunpy and astropy, both of which probably would've worked but I didn't see them at first and they look quite complex for this simple task...

Owner

  • Name: Kyle Barron
  • Login: kylebarron
  • Kind: user
  • Location: New York, New York
  • Company: @developmentseed

Creating the next generation of geospatial data tools for Python & the browser with @geoarrow, GeoParquet & @georust

GitHub Events

Total
  • Watch event: 4
  • Issue comment event: 1
  • Fork event: 1
Last Year
  • Watch event: 4
  • Issue comment event: 1
  • Fork event: 1

Committers

Last synced: about 1 year ago

All Time
  • Total Commits: 38
  • Total Committers: 5
  • Avg Commits per committer: 7.6
  • Development Distribution Score (DDS): 0.158
Past Year
  • Commits: 1
  • Committers: 1
  • Avg Commits per committer: 1.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Kyle Barron k****2@g****m 32
Vincent Sarago v****o@g****m 2
Balazs Varga n****c 2
lulunac27a n****s@g****m 1
jindal12ketan 5****n 1

Issues and Pull Requests

Last synced: 10 months ago

All Time
  • Total issues: 12
  • Total pull requests: 7
  • Average time to close issues: 23 minutes
  • Average time to close pull requests: about 5 hours
  • Total issue authors: 12
  • Total pull request authors: 5
  • Average comments per issue: 2.17
  • Average comments per pull request: 1.43
  • Merged pull requests: 7
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 1
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 1
  • Pull request authors: 0
  • Average comments per issue: 1.0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • tedder (1)
  • nmaslarinos (1)
  • GalenReich (1)
  • jindal12ketan (1)
  • mrputter (1)
  • vincentsarago (1)
  • hugoledoux (1)
  • marcinpr (1)
  • kylebarron (1)
  • Hcreak (1)
  • MLUY (1)
Pull Request Authors
  • nyooc (2)
  • vincentsarago (2)
  • jindal12ketan (1)
  • kylebarron (1)
  • lulunac27a (1)
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 11,354 last-month
  • Total dependent packages: 3
  • Total dependent repositories: 6
  • Total versions: 4
  • Total maintainers: 1
pypi.org: suncalc

A fast, vectorized Python port of suncalc.js

  • Versions: 4
  • Dependent Packages: 3
  • Dependent Repositories: 6
  • Downloads: 11,354 Last month
Rankings
Dependent packages count: 3.1%
Downloads: 4.4%
Dependent repos count: 6.0%
Average: 7.4%
Stargazers count: 10.2%
Forks count: 13.3%
Maintainers (1)
Last synced: 6 months ago

Dependencies

.github/workflows/test.yml actions
  • actions/checkout v2.1.1 composite
  • actions/setup-python v2 composite
setup.py pypi