Git Product home page Git Product logo

suncalc-py's Introduction

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:

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.

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']))
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:

    # (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')
    ]

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.

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! 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!!

%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...

suncalc-py's People

Contributors

jindal12ketan avatar kylebarron avatar lulunac27a avatar nyooc avatar vincentsarago avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

suncalc-py's Issues

Timezone problem

There is a problem to convert times received from get_times() to format "%H:%M:%S %Z" in other time zones than UTC.
I assume that:
sunrise = get_times(date, lat, lon)['sunrise']
saves the sunrise time with local zone instead of UTC.
Maybe the objects returned by get_times should have timezone (UTC) specified?
Please compare:
print(get_times(date, lat, lon)['sunrise'].strftime('%H:%M:%S %Z'))
print(datetime.now().strftime('%H:%M:%S %Z'))
Both gives the answer without timezone info.

How to print the sunrise time, adding timezone info?

DataFrame argument returns wrong solar positions

When using DataFrame arguments in the get_positions functions, the resulting solar positions are shifted by one hour compared to calling the function in a loop with singular datetime arguments. I double-checked the validity of the results using sunclac. The mistake appears to be in the to_milliseconds function.

Here's the code for the singular datetime loop:

import datetime
for i in range(8, 18):
    date = datetime.datetime(2023, 11, 3, i, 00)
    centroid_dict = {
        'lng' = 6.6
        'lat' = 46.5
    }
    azimuth, altitude = suncalc.get_position(date, **centroid_dict).values()

and here is the code creating a DataFrame and passing its elements as arguments:

import datetime
datetimes = []
    for hour in range(8, 18):
        datetimes.append(datetime.datetime(2023, 11, 3, hour))
df = pd.DataFrame({
    'date': datetimes,
    'lon': 6.6* len(datetimes),
    'lat': 46.5 * len(datetimes),
})
positions = suncalc.get_position(df["date"], lat=df["lat"], lng=df["lon"])

The first snippet returns the following results:

Solar altitude for 2023-11-03 08:00:00 is 5.842° (0.102rad) and azimuth is -61.096° (-1.066rad)
Solar altitude for 2023-11-03 09:00:00 is 14.287° (0.249rad) and azimuth is -49.119° (-0.857rad)
Solar altitude for 2023-11-03 10:00:00 is 21.236° (0.371rad) and azimuth is -35.750° (-0.624rad)
Solar altitude for 2023-11-03 11:00:00 is 26.118° (0.456rad) and azimuth is -20.837° (-0.364rad)
Solar altitude for 2023-11-03 12:00:00 is 28.391° (0.496rad) and azimuth is -4.724° (-0.082rad)
Solar altitude for 2023-11-03 13:00:00 is 27.749° (0.484rad) and azimuth is 11.695° (0.204rad)
Solar altitude for 2023-11-03 14:00:00 is 24.283° (0.424rad) and azimuth is 27.364° (0.478rad)
Solar altitude for 2023-11-03 15:00:00 is 18.427° (0.322rad) and azimuth is 41.608° (0.726rad)
Solar altitude for 2023-11-03 16:00:00 is 10.757° (0.188rad) and azimuth is 54.321° (0.948rad)
Solar altitude for 2023-11-03 17:00:00 is 1.816° (0.032rad) and azimuth is 65.802° (1.148rad)

while the second one returns the following:

Solar altitude for 2023-11-3  8:00:00 is 14.287° (0.249rad) and azimuth is -49.119° (-0.857rad)
Solar altitude for 2023-11-3  9:00:00 is 21.236° (0.371rad) and azimuth is -35.750° (-0.624rad)
Solar altitude for 2023-11-3 10:00:00 is 26.118° (0.456rad) and azimuth is -20.837° (-0.364rad)
Solar altitude for 2023-11-3 11:00:00 is 28.391° (0.496rad) and azimuth is -4.724° (-0.082rad)
Solar altitude for 2023-11-3 12:00:00 is 27.749° (0.484rad) and azimuth is 11.695° (0.204rad)
Solar altitude for 2023-11-3 13:00:00 is 24.283° (0.424rad) and azimuth is 27.364° (0.478rad)
Solar altitude for 2023-11-3 14:00:00 is 18.427° (0.322rad) and azimuth is 41.608° (0.726rad)
Solar altitude for 2023-11-3 15:00:00 is 10.757° (0.188rad) and azimuth is 54.321° (0.948rad)
Solar altitude for 2023-11-3 16:00:00 is 1.816° (0.032rad) and azimuth is 65.802° (1.148rad)
Solar altitude for 2023-11-3 17:00:00 is -7.944° (-0.139rad) and azimuth is 76.536° (1.336rad)

Pandas NaT times cause unexpected behaviour

Currently if a datetime holding the NaT value (supported by Numpy and Pandas) is passed to get_position it returns results for 12:12:43 UTC on Tuesday, September 21, 1677!

Minimum example to reproduce:

import pandas as pd
from suncalc import get_position

# Create DatetimeIndex containing NaTs
test_pandas_df = pd.to_datetime([None, None], unit="s", utc=True) 

print(get_position(test_pandas_df, 0, 0))

Gives: {'azimuth': Index([-0.9723467728956097, -0.9723467728956097], dtype='float64'), 'altitude': Index([-1.5207425775049375, -1.5207425775049375], dtype='float64')}

The bug is introduced by the coercion in get_milliseconds, which returns -9223372036854.775 (ms) for NaT values. This time offset from the UNIX epoch corresponds to the above date in 1677!

# Pandas series of Pandas datetime objects
if pd and pd.api.types.is_datetime64_any_dtype(date):
# A datetime-like series coerce to int is (always?) in nanoseconds
return date.astype('int64') / 10 ** 6

Instead, it would be great if NaT values were handled gracefully and None or np.nan values were returned instead. Alternatively, an error could be raised indicating that NaT values are unsupported.

lat-long vs long-lat && azimuth starting from South is just confusing

Very useful package, but 2 things that should be made clear in the docs IMO:

  1. you point to suncalc-js as a reimplementation, but the function there takes lat-long as input, and you take long-lat... 100% prefer long-lat, but this is somewhat confusing.
  2. the azimuth given is not CW from the North (as an azimuth is usually), but from the South and CW, and in radians. This is not explained anywhere, and I had to reverse-engineer this and then saw it at https://github.com/mourner/suncalc#sun-position, but then you think: if the author swapped lat-long, maybe they also corrected the azimuth from the South.

Hope this helps

Missing CHANGELOG.md

pip --no-cache-dir install suncalc
Collecting suncalc
  Downloading suncalc-0.1.0.tar.gz (9.7 kB)
    ERROR: Command errored out with exit status 1:
     command: /Users/vincentsarago/Workspace/venv/py37/bin/python3.7 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/s1/0w9w64dd7gl6jvh5j9b_dp_80000gn/T/pip-install-wag1jlc3/suncalc/setup.py'"'"'; __file__='"'"'/private/var/folders/s1/0w9w64dd7gl6jvh5j9b_dp_80000gn/T/pip-install-wag1jlc3/suncalc/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/s1/0w9w64dd7gl6jvh5j9b_dp_80000gn/T/pip-pip-egg-info-bpln2bvk
         cwd: /private/var/folders/s1/0w9w64dd7gl6jvh5j9b_dp_80000gn/T/pip-install-wag1jlc3/suncalc/
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/s1/0w9w64dd7gl6jvh5j9b_dp_80000gn/T/pip-install-wag1jlc3/suncalc/setup.py", line 9, in <module>
        with open('CHANGELOG.md') as history_file:
    FileNotFoundError: [Errno 2] No such file or directory: 'CHANGELOG.md'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

Confirmed when running python setup.py sdist, it doesn't ship the CHANGELOG.md

python setup.py sdist    
running sdist
running egg_info
creating suncalc.egg-info
writing suncalc.egg-info/PKG-INFO
writing dependency_links to suncalc.egg-info/dependency_links.txt
writing requirements to suncalc.egg-info/requires.txt
writing top-level names to suncalc.egg-info/top_level.txt
writing manifest file 'suncalc.egg-info/SOURCES.txt'
reading manifest file 'suncalc.egg-info/SOURCES.txt'
writing manifest file 'suncalc.egg-info/SOURCES.txt'
running check
creating suncalc-0.1.0
creating suncalc-0.1.0/suncalc
creating suncalc-0.1.0/suncalc.egg-info
copying files to suncalc-0.1.0...
copying README.md -> suncalc-0.1.0
copying setup.cfg -> suncalc-0.1.0
copying setup.py -> suncalc-0.1.0
copying suncalc/__init__.py -> suncalc-0.1.0/suncalc
copying suncalc/suncalc.py -> suncalc-0.1.0/suncalc
copying suncalc.egg-info/PKG-INFO -> suncalc-0.1.0/suncalc.egg-info
copying suncalc.egg-info/SOURCES.txt -> suncalc-0.1.0/suncalc.egg-info
copying suncalc.egg-info/dependency_links.txt -> suncalc-0.1.0/suncalc.egg-info
copying suncalc.egg-info/not-zip-safe -> suncalc-0.1.0/suncalc.egg-info
copying suncalc.egg-info/requires.txt -> suncalc-0.1.0/suncalc.egg-info
copying suncalc.egg-info/top_level.txt -> suncalc-0.1.0/suncalc.egg-info
Writing suncalc-0.1.0/setup.cfg
creating dist
Creating tar archive
removing 'suncalc-0.1.0' (and everything under it)

Wrong annotations are misleading

date (datetime or a pandas series of datetimes): date and time to find sunlight phases of. Datetime must be in UTC.

This comment made me use datetime.utcnow() incorrectly.
And actually need to pass the local time like in your example code using datetime.now()

arccos errors in 0.1.2

This seems to be related or similar to #4. (note I manually wordwrapped these for readability)

$ python3
Python 3.8.10 (default, Nov 26 2021, 20:14:08) 
[GCC 9.3.0] on linux
>>> suncalc.__version__
'0.1.2'
>>> import datetime,suncalc
>>> datetime.datetime.strptime('20211220', '%Y%m%d')
datetime.datetime(2021, 12, 20, 0, 0)
>>> suncalc.get_times( datetime.datetime.strptime('2021220', '%Y%m%d'), 51, -114)
/usr/local/lib/python3.8/dist-packages/suncalc/suncalc.py:202:
RuntimeWarning: invalid value encountered in arccos
  return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)))
{'solar_noon': datetime.datetime(2021, 2, 20, 8, 51, 12, 961660),
'nadir': datetime.datetime(2021, 2, 19, 20, 51, 12, 961660),
'sunrise': datetime.datetime(2021, 2, 20, 4, 43, 14, 252018),
'sunset': datetime.datetime(2021, 2, 20, 12, 59, 11, 671301),
'sunrise_end': datetime.datetime(2021, 2, 20, 4, 37, 13, 961476),
'sunset_start': datetime.datetime(2021, 2, 20, 13, 5, 11, 961843),
'dawn': datetime.datetime(2021, 2, 20, 5, 47, 17, 217253),
'dusk': datetime.datetime(2021, 2, 20, 11, 55, 8, 706066),
'nautical_dawn': datetime.datetime(2021, 2, 20, 7, 41, 14, 97490),
'nautical_dusk': datetime.datetime(2021, 2, 20, 10, 1, 11, 825829),
'night_end': numpy.datetime64('NaT'),
'night': numpy.datetime64('NaT'),
'golden_hour_end': datetime.datetime(2021, 2, 20, 3, 30, 41, 914049),
'golden_hour': datetime.datetime(2021, 2, 20, 14, 11, 44, 9271)}

>>> suncalc.get_times( datetime.datetime.strptime('2021120', '%Y%m%d'), 51, -114)
{'solar_noon': datetime.datetime(2021, 1, 20, 8, 48, 6, 897451), 
'nadir': datetime.datetime(2021, 1, 19, 20, 48, 6, 897451),
'sunrise': datetime.datetime(2021, 1, 20, 6, 45, 44, 506256),
'sunset': datetime.datetime(2021, 1, 20, 10, 50, 29, 288645),
'sunrise_end': datetime.datetime(2021, 1, 20, 6, 35, 10, 891669),
'sunset_start': datetime.datetime(2021, 1, 20, 11, 1, 2, 903233),
'dawn': numpy.datetime64('NaT'), 
'dusk': numpy.datetime64('NaT'),
'nautical_dawn': numpy.datetime64('NaT'),
'nautical_dusk': numpy.datetime64('NaT'),
'night_end': numpy.datetime64('NaT'),
'night': numpy.datetime64('NaT'),
'golden_hour_end': datetime.datetime(2021, 1, 20, 5, 1, 19, 241467),
'golden_hour': datetime.datetime(2021, 1, 20, 12, 34, 54, 553435)}

>>> suncalc.get_times( datetime.datetime.utcnow(), 31, -114)
{'solar_noon': datetime.datetime(2021, 12, 21, 9, 55, 20, 995867),
'nadir': datetime.datetime(2021, 12, 20, 21, 55, 20, 995867),
'sunrise': numpy.datetime64('NaT'),
'sunset': numpy.datetime64('NaT'),
'sunrise_end': datetime.datetime(2021, 12, 21, 9, 19, 27, 169529),
'sunset_start': datetime.datetime(2021, 12, 21, 10, 31, 14, 822205),
'dawn': numpy.datetime64('NaT'),
'dusk': numpy.datetime64('NaT'),
'nautical_dawn': numpy.datetime64('NaT'),
'nautical_dusk': numpy.datetime64('NaT'),
'night_end': numpy.datetime64('NaT'),
'night': numpy.datetime64('NaT'),
'golden_hour_end': datetime.datetime(2021, 12, 21, 6, 51, 1, 218479),
'golden_hour': datetime.datetime(2021, 12, 21, 12, 59, 40, 773255)}

Note it only gives the warning once, but it happens in each- presumably numpy just suppresses the warnings after the first time. Here's an example- and note it isn't near the winter solstice.

>>> import datetime,suncalc
>>> suncalc.get_times( datetime.datetime(2021, 10, 21, 9, 55, 20, 995867), 51, -114)
/usr/local/lib/python3.8/dist-packages/suncalc/suncalc.py:202:
RuntimeWarning: invalid value encountered in arccos
  return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)))
{'solar_noon': datetime.datetime(2021, 10, 21, 8, 21, 47, 239111),
'nadir': datetime.datetime(2021, 10, 20, 20, 21, 47, 239111),
'sunrise': datetime.datetime(2021, 10, 21, 4, 11, 27, 472661),
'sunset': datetime.datetime(2021, 10, 21, 12, 32, 7, 5561),
'sunrise_end': datetime.datetime(2021, 10, 21, 4, 5, 29, 309365),
'sunset_start': datetime.datetime(2021, 10, 21, 12, 38, 5, 168856),
'dawn': datetime.datetime(2021, 10, 21, 5, 14, 55, 726919),
'dusk': datetime.datetime(2021, 10, 21, 11, 28, 38, 751302),
'nautical_dawn': datetime.datetime(2021, 10, 21, 7, 4, 56, 97234),
'nautical_dusk': datetime.datetime(2021, 10, 21, 9, 38, 38, 380988),
'night_end': numpy.datetime64('NaT'),
'night': numpy.datetime64('NaT'),
'golden_hour_end': datetime.datetime(2021, 10, 21, 2, 59, 12, 620696),
'golden_hour': datetime.datetime(2021, 10, 21, 13, 44, 21, 857526)}

Some of the problems seem to be solstice-related:

>>> suncalc.get_times( datetime.datetime(2021, 12, 20, 20, 21, 47, 239111), 31, -114)['dawn']
numpy.datetime64('NaT')
>>> suncalc.get_times( datetime.datetime(2021, 10, 20, 20, 21, 47, 239111), 31, -114)['dawn']
datetime.datetime(2021, 10, 21, 6, 35, 10, 705027)

RuntimeWarning: invalid value encountered in arccos

This seems a little strange; presumably some calculation is ending up trying to take arccos of a number greater than 1 (or less than -1), which might indicate a more significant issue.

from datetime import datetime
from suncalc import get_times

date = datetime(2023, 5, 24)
lon = 0
lat = 51.5
get_times(date, lon, lat)

Running this (with suncalc 0.1.3 from PyPI) gives:

/<PATH>/suncalc-env/lib/python3.11/site-packages/suncalc/suncalc.py:202: RuntimeWarning: invalid value encountered in arccos
  return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)))

Receiving a "numpy.float64 object is not iterable" exception in from_julian when calling get_times() near the summer solstice

Trying to use the package to calculate various times throughout the year for Calgary Canada (51.0447 lat, -114.0719 long).

get_times() works fine for most of the year, but fails when within ~ 26 days of the summer solstice (specifically ~2AM May 25 through 2AM July 18, 2020; the range is slightly different in other years).

"Failure" manifests as a 'numpy.float64' object is not iterable TypeError in from_julian() (line 111 of suncalc.py).

Trace/error message/steps to reproduce follow:

Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from suncalc import get_times
>>> from datetime import datetime
>>> get_times(datetime(2020,5,26,0,0,0), -114.0719, 51.0447)
Traceback (most recent call last):
  File "C:\Program Files\Python38\lib\site-packages\suncalc\suncalc.py", line 111, in from_julian
    return np.array([datetime.utcfromtimestamp(x / 1000) for x in ms_date])
TypeError: 'numpy.float64' object is not iterable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files\Python38\lib\site-packages\suncalc\suncalc.py", line 291, in get_times
    result[time[1]] = from_julian(Jrise[idx])
  File "C:\Program Files\Python38\lib\site-packages\suncalc\suncalc.py", line 114, in from_julian
    return datetime.utcfromtimestamp(ms_date / 1000)
ValueError: Invalid value NaN (not a number)
>>>

Suncalc 0.1.1 (freshly installed):

C:\>pip list | findstr "suncalc numpy"
numpy                     1.19.4
suncalc                   0.1.1

Moon Illumination Issue

Here in function for Moon Illumination there should be some changes to be happen:

  1. inc = atan(sdist * sin(phi), m['dist'] - sdist * cos(phi)), # comma should not be there after calculating atan
  2. sdist = 149597870.7 position of sun should not be constant for given date

to calculate the distance between sun and earth we should use true_anomaly of sun like below.

`def true_anomaly(days):
M = solar_mean_anomaly(days)
E = M
epsilon = 1e-6
max_iterations = 100
for _ in range(max_iterations):
E_new = E - (E - e * np.sin(E) - M) / (1 - e * np.cos(E))
if abs(E_new - E) < epsilon:
break
E = E_new

theta = 2 * np.arctan(np.sqrt((1 + e) / (1 - e)) * np.tan(E / 2))
return theta`

Vectorisation all casts to same datetime

Hi Kyle
First of all thanks for this great package!
I am having trouble to get the vectorization to work as per your example
I have a pandas date frame with a 'Date' column of (datetimes) and a long and lat (floats)
When I run the vector operation I am getting a single date back (ref attachment)

What could I be doing wrong?

Thanks a lot
Michiel

cast1969

Email bug report

From email:

I've been playing around with your suncalc module and think I have
encountered a systematic error, here are the details and I've attached
my program.

  1. Use get_times to get the 'solar_noon' data for the June solstice

  2. Feed that time into get_position to get Azimuth & Altitude data

The Azimuth at such a time should either be 0 or PI but that was not
what I was seeing

Doing the same for Dec solstice didn't produce such an error [at least
for the value of long & lat I was using to understand the issue - 0,0]

Eventually, I hypothesised that there might be a screwup in interpreting
the time [by me or the function] and tried altering the hour of the
solar noon as given and adjusting by +1 hour seemed to fix the problem.
I ported the fix in to my original intention for the program [check
difference between altitude angles at solar noon on solstices is a
constant where ever you are] and could demonstrate that this is an issue
at all the locations in the set of co-ords I was generating.

In the program you'll find fix_offset which is currently set to 1 and
thus correctly generates correct values. Change this to 0 and the error
manifests itself.

Python Version: 3.8.5

Suncalc: 0.1.2 installed via pip3 on Mar 17th

Code:

#!/usr/bin/env python3
from suncalc import get_position, get_times
from datetime import datetime
from collections import namedtuple
import random, math

Sun_dates = namedtuple('Sun_dates','march_equinox june_solstice sept_equinox dec_solstice')

sun_dates_2021 = Sun_dates(datetime(2021,3,20),datetime(2021,6,21),datetime(2021,9,22),datetime(2021,12,22))

fix_offset = 1 # added to solar noon time to correct systematic error in summer soltice positon?

for lat in range(5): #range(20,70,10):
    lon = random.randint(0,100)
    lat = random.randint(0,60)
    print(lon,lat)

    june_solar_noon = get_times(sun_dates_2021.june_solstice,lon,lat)['solar_noon']
    print(june_solar_noon)
    
    dec_solar_noon = get_times(sun_dates_2021.dec_solstice,lon,lat)['solar_noon']
    print(dec_solar_noon)
    
    june_pos = get_position(june_solar_noon.replace(hour=june_solar_noon.hour + fix_offset),lng=lon,lat=lat)
    print(june_pos,math.degrees(june_pos['azimuth']),math.degrees(june_pos['altitude']))
    
    dec_pos = get_position(dec_solar_noon,lng=lon,lat=lat)
    print(dec_pos,math.degrees(dec_pos['azimuth']),math.degrees(dec_pos['altitude']))
    
    if abs(june_pos['azimuth']) > 1:
        if abs(dec_pos['azimuth']) > 1:
            max_elev = max(june_pos['altitude'],dec_pos['altitude'])
            min_elev = min(june_pos['altitude'],dec_pos['altitude'])
            print(math.degrees(max_elev - min_elev))
        else: 
            print(math.degrees(math.pi - june_pos['altitude'] - dec_pos['altitude']))
    elif abs(dec_pos['azimuth']) > 1:
        print(math.degrees(math.pi - june_pos['altitude'] - dec_pos['altitude']))
    else:
        max_elev = max(june_pos['altitude'],dec_pos['altitude'])
        min_elev = min(june_pos['altitude'],dec_pos['altitude'])
        print(math.degrees(max_elev - min_elev))
    print('===') 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.