A free, fast and accurate Python package to compute the position of the Sun
SolTrack for Python
A free, fast and simple Python package to compute the position of the Sun, as well as its rise and set times. SolTrack was originally written in C/C++ by Marc van der Sluys of the department of astrophysics of the Radboud University Nijmegen, the Netherlands and the Sustainable energy research group of the HAN University of Applied Sciences in Arnhem, the Netherlands (now at the Netherlands Institute for Nuclear and High-Energy Physics (Nikhef) and the Institute for Gravitational and Subatomic Physics (GRASP) at Utrecht University in the Netherlands), and Paul van Kan of the Sustainable energy research group of the HAN University of Applied Sciences in Arnhem, the Netherlands. The code has now been translated to pure Python and can be used under the conditions of the EUPL 1.2 licence.
SolTrack can perform up to 1.5 million position calculations per second on a single 3.4 GHz core of my laptop (the C version of SolTrack is about twice as fast) with an accuracy of 0.0030 ± 0.0016°.
This package can be installed using
pip install soltrack. This should automatically install the dependency
astrotool if they haven't been installed already (if you're not
using a Python version older than 3.7, you will need to install
dataclasses as well). If you are installing
by hand, ensure that these packages are installed as well.
Update from v0.1.x to v0.2.0
The update from SolTrack v0.1.4 to v0.3.0 is not backwards compatible. Most public members/variables and
methods/functions, as well as dummy arguments have been renamed in order to comply better with the Python
standards. I have tried to put as much nuisance into a single update as possible (as opposed to in multiple
updates, rather than as opposed to as little nuisance as possible), so that this will hopefully not happen
again in the future. For public methods, the obsolescent old name is kept as an alias, and warnings are
issued when used, instructing how to adapt your code. In many cases a variable or function name
will have been replaced to one
like_this, i.e. upper case replaced with an underscore and lower case. See
the documentation for more details.
Code example for a number or range of datetimes
# Create a Pandas DatetimeIndex range every 20 days 1hour and 10 minutes, in my timezone: import pandas as pd dti = pd.date_range('2022-01-01 01:00', '2022-12-31 23:00', freq='20d1h10min', tz='Europe/Amsterdam') # Set the geographic location to Arnhem, the Netherlands (we'll use degrees in SolTrack): geo_lon = 5.950270 # Positive -> east of Greenwich (degrees) geo_lat = 51.987380 # Positive -> northern hemisphere (degrees) # Create a SolTrack instance and specify preferences: from soltrack import SolTrack st = SolTrack(geo_lon, geo_lat, use_degrees=True) # Use default values for all but use_degrees st.set_date_time(dti) # Pass my dates and times to SolTrack st.compute_position() # Compute the Sun's position st.compute_rise_set() # Compute the rise and set times of the Sun # Print some selected results as arrays and create chaos: if st.lt is not None: # If local time was used print('Local time: ', *st.lt) # Asterisk (*) unpacks the DTI print('UTC: ', *st.utc) print('azimuth: ', *st.azimuth) print('altitude: ', *st.altitude) print('distance: ', *st.distance) print('riseTime: ', *st.rise_time) print('transTime: ', *st.transit_time) print('setTime: ', *st.set_time) # Store selected results in a Pandas DataFrame and print that in a more orderly fashion: st.create_df(utc=True, jd=True, ecl=True, rts_pos=True) with pd.option_context('display.max_columns',None, 'display.width',None): # Want all columns print(st.df)
Code example for a single instant
Note that in most cases, you should use the vector option instead, as it is faster starting from calculations for two instances. See the section Performance for more details. The code listing below is provided for completeness only.
"""Example Python script to compute the position of the Sun and its rise and set times for a single instant and demonstrate some other features.""" from soltrack import SolTrack import datetime as dt import pytz as tz # Set the geographic location to Arnhem, the Netherlands: geo_lon = 5.950270 # Positive -> east of Greenwich (degrees) geo_lat = 51.987380 # Positive -> northern hemisphere (degrees) st = SolTrack(geo_lon, geo_lat, use_degrees=True) # Same as above, using default values for all but use_degrees. # Set SolTrack date and time using separate (UTC!) year, month, day, hour, minute and second variables: st.set_date_and_time(2023, 7, 16, 6, 2, 49.217348) # Date: 2023-07-16, time: 06:02:49.217348 UTC # Alternatively, use a (localised) datetime object: cet = tz.timezone('Europe/Amsterdam') my_date = dt.datetime(2023, 7, 16, 8, 2, 49, 217348) # Same as above, in local time for TZ=+2 (08:02:49.217348 LT) my_date = cet.localize(my_date) st.set_date_time(my_date) # Set SolTrack date and time using a Python datetime object. # Compute the Sun's position: st.compute_position() # Compute the rise and set times of the Sun: st.compute_rise_set() # Write results to standard output: print("Location: %0.3lf E, %0.3lf N" % (st.geo_longitude, st.geo_latitude)) # print("Date/time: %4d-%02d-%02d %02d:%02d:%09.6lf" % (st.year, st.month, st.day, st.hour, st.minute, st.second)) print("Date/time: %s" % my_date) print("JD: %0.11lf" % (st.julian_day)) print() print("Ecliptic longitude, latitude: %10.6lf° %10.6lf°" % (st.longitude, 0.0)) # Note: latitude is always 0 in this model print("Distance: %10.6lf°" % (st.distance)) print("Right ascension, declination: %10.6lf° %10.6lf°" % (st._right_ascension_uncorr, st._declination_uncorr)) print("Uncorrected altitude: %10.6lf°" % (st._altitude_uncorr)) print("Corrected azimuth, altitude: %10.6lf° %10.6lf°" % (st.azimuth, st.altitude)) print("Corrected hour angle, declination: %10.6lf° %10.6lf°" % (st.hour_angle, st.declination)) print() print("Rise time: %s, azimuth: %11.5lf" % (*st.rise_time, *st.rise_azimuth)) print("Transit time: %s, altitude: %11.5lf" % (*st.transit_time, *st.transit_altitude)) print("Set time: %s, azimuth: %11.5lf" % (*st.set_time, *st.set_azimuth)) print() # Change the location whilst keeping the same SolTrack object: st.set_location(geo_lon, -geo_lat) # Compute the current position of the Sun for the new location: st.now() st.compute_position() print("Location: %0.3lf E, %0.3lf N" % (st.geo_longitude, st.geo_latitude)) print("Date (UT): %4d-%02d-%02d" % (st.year, st.month, st.day)) print("Time (UT): %02d:%02d:%09.6lf" % (st.hour, st.minute, st.second)) print("Corrected azimuth, altitude: %10.6lf° %10.6lf°" % (st.azimuth, st.altitude))
Code example for SolTrack v0.1.x (old)
"""Example Python script to compute the position of the Sun and its rise and set times for a single instant.""" import soltrack as st # Set preferences (all are False by default): useDegrees = True # Input (geographic position) and output are in degrees useNorthEqualsZero = True # Azimuth: 0 = South, pi/2 (90deg) = West -> 0 = North, pi/2 (90deg) = East computeRefrEquatorial = True # Compure refraction-corrected equatorial coordinates (Hour angle, declination) computeDistance = True # Compute the distance to the Sun # Set up geographical location (in degrees, since useDegrees=True) in a SolTrack Location dataclass object: loc = st.Location(5.950270, 51.987380) # longitude (>0: east of Greenwich), latitude (>0: northern hemisphere) # Set (UT!) date and time in a SolTrack Time dataclass object: time = st.Time(2045, 7, 16, 6, 2, 49.217348) # Date: 2045-07-16, time: 06:02:49.217348 # Compute positions - returns a st.Position object: pos = st.computeSunPosition(loc, time, useDegrees, useNorthEqualsZero, computeRefrEquatorial, computeDistance) # Compute rise and set times - returns a st.RiseSet object: riseSet = st.computeSunRiseSet(loc, time, 0.0, useDegrees, useNorthEqualsZero) # Write results to standard output: print("Location: %0.3lf E, %0.3lf N" % (loc.longitude, loc.latitude)) print("Date: %4d %2d %2d" % (time.year, time.month, time.day)) print("Time: %2d %2d %9.6lf" % (time.hour, time.minute, time.second)) print("JD: %0.11lf" % (pos.julianDay)) print() print("Ecliptic longitude, latitude: %10.6lf° %10.6lf°" % (pos.longitude, 0.0)) print("Right ascension, declination: %10.6lf° %10.6lf°" % (pos.rightAscension, pos.declination)) print("Uncorrected altitude: %10.6lf°" % (pos.altitude)) print("Corrected azimuth, altitude: %10.6lf° %10.6lf°" % (pos.azimuthRefract, pos.altitudeRefract)) print("Corrected hour angle, declination: %10.6lf° %10.6lf°" % (pos.hourAngleRefract, pos.declinationRefract)) print() print("Rise time: %11.5lf, azimuth: %11.5lf" % (riseSet.riseTime, riseSet.riseAzimuth)) print("Transit time: %11.5lf, altitude: %11.5lf" % (riseSet.transitTime, riseSet.transitAltitude)) print("Set time: %11.5lf, azimuth: %11.5lf" % (riseSet.setTime, riseSet.setAzimuth)) print()
Performance of position calculations for an array/vector or range of instants
From v0.2.0 on, SolTrack uses Pandas to allow the use of arrays or vectors of datetimes (Series, DatetimeIndex, ndarrays of datetime64, ...). This causes some overhead, which is relatively more significant for small numbers. SolTrack performs fastest (per position calculation and on my laptop) for about 105-106 calculations:
|Ncalc (-)||Time (s)||Speed (/s)||Speed (millions/s)|
These benchmarks were done on a single CPU core, capped at 3.4GHz. The cpu and core were always the same, and the minimum of 10 benchmarks is listed. Timezone-naive datetimes (representing UTC) were used; using timezone-aware datetimes slows down the code by a factor of ~4.6 compared to the numbers in the table.
Performance of calculations of rise, transit and set times
Benchmarks for rise, transit and set computations were performed in the same manner as above, but using
return_datetimes=False in the
st.compute_rise_set() call, which returns rise, transit and set times in
decimal hours. If instead datetimes are desired, the code becomes a factor of about 3.1 times slower. The
utc=True has little effect, and using timezone-aware datetimes instead of timezone-naive ones adds
6.1% to the computational times listed below.
|Ncalc (-)||Time (s)||Speed (/s)|
Performance of position calculation for a single instant
Because of the overhead of Pandas and datetime-like objects, SolTrack has actually slowed down by a factor of ~10.4 between versions 0.1.4 and 0.2.0 when doing single calculations. I don't consider that a big issue, since a single position calculation still takes less than a millisecond, so that the effect is not noticable for humans. It does become noticable for large numbers, but then arrays can be used, which make the code ~1000x faster. In fact, the array version is faster starting from two iterations, so there is usually very little reason to use the single-datetime option.
Single calculations can be made about 19% faster by providing datetimes in UTC and specifying
st.set_date_time() method. The use of timezone-naive versus timezone-aware datetimes has little
influence on the performance for single calculations, and calculation times scale linearly when doing multiple
calls (since the must then be done in a Python loop).
For single datetimes, 1000 calculations take about 0.68 seconds on the single CPU core of my laptop capped at
3.4 GHz, and about 0.55 seconds with
Pypi: SolTrack Python package
GitHub: SolTrack Python source code
ReadTheDocs: SolTrack Python documentation
SolTrack for C/C++ on SourceForge
SolTrack for Arduino on GitHub
Author and licence
- AstroConst: a Python package that provides astronomical constants.
- AstroTool: a Python package for astronomical calculations in Python or on the command line.
- elp-mpp02: accurate Moon positions using the lunar solution ELP/MPP02 in Python.
- libTheSky: a Fortran library to compute the positions of celestial bodies (Sun, Moon, planets, stars, asteroids, comets) and events (e.g. lunar phases) with great accuracy.
- SolarEnergy: A Python module to do simple modelling in the field of solar energy.
- Celestial mechanics in a nutshell (CMiaNS): online living document.
- Meeus, Astronomical algorithms, 2nd Ed.
- The C/C++ and Python codes have been adapted from the Fortran implementation in libTheSky, which contains many references.
Copyright (c) 2019-2022 Marc van der Sluys
Release history Release notifications | RSS feed
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.