{"id":18174852,"url":"https://github.com/kylebarron/suncalc-py","last_synced_at":"2025-05-07T04:08:56.376Z","repository":{"id":55984589,"uuid":"314394435","full_name":"kylebarron/suncalc-py","owner":"kylebarron","description":"A Python port of suncalc.js for calculating sun position and sunlight phases","archived":false,"fork":false,"pushed_at":"2024-04-15T13:38:53.000Z","size":38,"stargazers_count":63,"open_issues_count":9,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-07T04:08:48.310Z","etag":null,"topics":["numpy","sun","sunrise","sunset"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kylebarron.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-19T23:27:02.000Z","updated_at":"2025-04-19T08:34:52.000Z","dependencies_parsed_at":"2024-04-15T15:17:25.856Z","dependency_job_id":"34e1aad0-fe55-476e-8fe4-fc82dd4716b9","html_url":"https://github.com/kylebarron/suncalc-py","commit_stats":{"total_commits":38,"total_committers":5,"mean_commits":7.6,"dds":0.1578947368421053,"last_synced_commit":"6568431fe494393774b1ee34647d8cab35b5141d"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kylebarron%2Fsuncalc-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kylebarron%2Fsuncalc-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kylebarron%2Fsuncalc-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kylebarron%2Fsuncalc-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kylebarron","download_url":"https://codeload.github.com/kylebarron/suncalc-py/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252810273,"owners_count":21807759,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["numpy","sun","sunrise","sunset"],"created_at":"2024-11-02T16:07:58.446Z","updated_at":"2025-05-07T04:08:56.353Z","avatar_url":"https://github.com/kylebarron.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# suncalc-py\n\n\u003cp\u003e\n  \u003ca href=\"https://github.com/kylebarron/suncalc-py/actions?query=workflow%3ACI\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://github.com/kylebarron/suncalc-py/workflows/test/badge.svg\" alt=\"Test\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pypi.org/project/suncalc\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://img.shields.io/pypi/v/suncalc?color=%2334D058\u0026label=pypi%20package\" alt=\"Package version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/kylebarron/suncalc-py/blob/master/LICENSE\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/license/kylebarron/suncalc-py.svg\" alt=\"Downloads\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\nA fast, vectorized Python implementation of [`suncalc.js`][suncalc-js] for\ncalculating sun position and sunlight phases (times for sunrise, sunset, dusk,\netc.) for the given location and time.\n\n[suncalc-js]: https://github.com/mourner/suncalc\n\nWhile other similar libraries exist, I didn't originally encounter any that met\nmy requirements of being openly-licensed, vectorized, and simple to use \u003csup\u003e1\u003c/sup\u003e.\n\n## Install\n\n```\npip install suncalc\n```\n\n## Using\n\n### Example\n\n`suncalc` is designed to work both with single values and with arrays of values.\n\nFirst, import the module:\n\n```py\nfrom suncalc import get_position, get_times\nfrom datetime import datetime\n```\n\nThere are currently two methods: `get_position`, to get the sun azimuth and\naltitude (in radians) for a given date and position, and `get_times`, to get sunlight phases\nfor a given date and position.\n\n```py\ndate = datetime.now()\nlon = 20\nlat = 45\nget_position(date, lon, lat)\n# {'azimuth': -0.8619668996997687, 'altitude': 0.5586446727994595}\n\nget_times(date, lon, lat)\n# {'solar_noon': Timestamp('2020-11-20 08:47:08.410863770'),\n#  'nadir': Timestamp('2020-11-19 20:47:08.410863770'),\n#  'sunrise': Timestamp('2020-11-20 03:13:22.645455322'),\n#  'sunset': Timestamp('2020-11-20 14:20:54.176272461'),\n#  'sunrise_end': Timestamp('2020-11-20 03:15:48.318936035'),\n#  'sunset_start': Timestamp('2020-11-20 14:18:28.502791748'),\n#  'dawn': Timestamp('2020-11-20 02:50:00.045539551'),\n#  'dusk': Timestamp('2020-11-20 14:44:16.776188232'),\n#  'nautical_dawn': Timestamp('2020-11-20 02:23:10.019832520'),\n#  'nautical_dusk': Timestamp('2020-11-20 15:11:06.801895264'),\n#  'night_end': Timestamp('2020-11-20 01:56:36.144269287'),\n#  'night': Timestamp('2020-11-20 15:37:40.677458252'),\n#  'golden_hour_end': Timestamp('2020-11-20 03:44:46.795967773'),\n#  'golden_hour': Timestamp('2020-11-20 13:49:30.025760010')}\n```\n\nThese methods also work for _arrays_ of data, and since the implementation is\nvectorized it's much faster than a for loop in Python.\n\n```py\nimport pandas as pd\n\ndf = pd.DataFrame({\n    'date': [date] * 10,\n    'lon': [lon] * 10,\n    'lat': [lat] * 10\n})\npd.DataFrame(get_position(df['date'], df['lon'], df['lat']))\n# azimuth\taltitude\n# 0\t-1.485509\t-1.048223\n# 1\t-1.485509\t-1.048223\n# ...\n\npd.DataFrame(get_times(df['date'], df['lon'], df['lat']))['solar_noon']\n# 0   2020-11-20 08:47:08.410863872+00:00\n# 1   2020-11-20 08:47:08.410863872+00:00\n# ...\n# Name: solar_noon, dtype: datetime64[ns, UTC]\n```\n\nIf you want to join this data back to your `DataFrame`, you can use `pd.concat`:\n\n```py\ntimes = pd.DataFrame(get_times(df['date'], df['lon'], df['lat']))\npd.concat([df, times], axis=1)\n```\n\n### API\n\n#### `get_position`\n\nCalculate sun position (azimuth and altitude) for a given date and\nlatitude/longitude\n\n- `date` (`datetime` or a pandas series of datetimes): date and time to find sun position of. **Datetime must be in UTC**.\n- `lng` (`float` or numpy array of `float`): longitude to find sun position of\n- `lat` (`float` or numpy array of `float`): latitude to find sun position of\n\nReturns a `dict` with two keys: `azimuth` and `altitude`. If the input values\nwere singletons, the `dict`'s values will be floats. Otherwise they'll be numpy\narrays of floats.\n\n#### `get_times`\n\n- `date` (`datetime` or a pandas series of datetimes): date and time to find sunlight phases of. **Datetime must be in UTC**.\n- `lng` (`float` or numpy array of `float`): longitude to find sunlight phases of\n- `lat` (`float` or numpy array of `float`): latitude to find sunlight phases of\n- `height` (`float` or numpy array of `float`, default `0`): observer height in meters\n- `times` (`Iterable[Tuple[float, str, str]]`): an iterable defining the angle above the horizon and strings for custom sunlight phases. The default is:\n\n    ```py\n    # (angle, morning name, evening name)\n    DEFAULT_TIMES = [\n        (-0.833, 'sunrise', 'sunset'),\n        (-0.3, 'sunrise_end', 'sunset_start'),\n        (-6, 'dawn', 'dusk'),\n        (-12, 'nautical_dawn', 'nautical_dusk'),\n        (-18, 'night_end', 'night'),\n        (6, 'golden_hour_end', 'golden_hour')\n    ]\n    ```\n\nReturns a `dict` where the keys are `solar_noon`, `nadir`, plus any keys passed\nin the `times` argument. If the input values were singletons, the `dict`'s\nvalues will be of type `datetime.datetime` (or `pd.Timestamp` if you have pandas\ninstalled, which is a subclass of and therefore compatible with\n`datetime.datetime`). Otherwise they'll be pandas `DateTime` series. **The\nreturned times will be in UTC.**\n\n## Benchmark\n\nThis benchmark is to show that the vectorized implementation is nearly 100x\nfaster than a for loop in Python.\n\nFirst set up a `DataFrame` with random data. Here I create 100,000 rows.\n\n```py\nfrom suncalc import get_position, get_times\nimport pandas as pd\n\ndef random_dates(start, end, n=10):\n    \"\"\"Create an array of random dates\"\"\"\n    start_u = start.value//10**9\n    end_u = end.value//10**9\n    return pd.to_datetime(np.random.randint(start_u, end_u, n), unit='s')\n\nstart = pd.to_datetime('2015-01-01')\nend = pd.to_datetime('2018-01-01')\ndates = random_dates(start, end, n=100_000)\n\nlons = np.random.uniform(low=-179, high=179, size=(100_000,))\nlats = np.random.uniform(low=-89, high=89, size=(100_000,))\n\ndf = pd.DataFrame({'date': dates, 'lat': lats, 'lon': lons})\n```\n\nThen compute `SunCalc.get_position` two ways: the first using the vectorized\nimplementation and the second using `df.apply`, which is equivalent to a for\nloop. The first is more than **100x faster** than the second.\n\n```py\n%timeit get_position(df['date'], df['lon'], df['lat'])\n# 41.4 ms ± 437 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n\n%timeit df.apply(lambda row: get_position(row['date'], row['lon'], row['lat']), axis=1)\n# 4.89 s ± 184 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n```\n\nLikewise, compute `SunCalc.get_times` the same two ways: first using the\nvectorized implementation and the second using `df.apply`. The first is **2800x\nfaster** than the second! Some of the difference here is that under the hood the\nnon-vectorized approach uses `pd.to_datetime` while the vectorized\nimplementation uses `np.astype('datetime64[ns, UTC]')`. `pd.to_datetime` is\nreally slow!!\n\n```py\n%timeit get_times(df['date'], df['lon'], df['lat'])\n# 55.3 ms ± 1.91 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n\n%time df.apply(lambda row: get_times(row['date'], row['lon'], row['lat']), axis=1)\n# CPU times: user 2min 33s, sys: 288 ms, total: 2min 34s\n# Wall time: 2min 34s\n```\n\n---\n\n1: [`pyorbital`](https://github.com/pytroll/pyorbital) looks great but is\nGPL3-licensed; [`pysolar`](https://github.com/pingswept/pysolar) is also\nGPL3-licensed; [`pyEphem`](https://rhodesmill.org/pyephem/) is LGPL3-licensed.\n[`suncalcPy`](https://github.com/Broham/suncalcPy) is another port of\n`suncalc.js`, and is MIT-licensed, but doesn't use Numpy and thus isn't\nvectorized. I recently discovered [`sunpy`](https://github.com/sunpy/sunpy) and\n[`astropy`](https://github.com/astropy/astropy), both of which probably would've\nworked but I didn't see them at first and they look quite complex for this\nsimple task...\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkylebarron%2Fsuncalc-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkylebarron%2Fsuncalc-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkylebarron%2Fsuncalc-py/lists"}