Home

SolarPosition.jl

Stable Development documentation

Test workflow status Coverage Lint workflow Status Docs workflow Status Aqua QA tested with JET.jl

SolarPosition.jl provides a simple, unified interface to a collection of validated solar position algorithms written in pure, performant julia.

Solar positioning algorithms are commonly used to calculate the solar zenith and azimuth angles, which are essential for various applications where the sun is important, such as:

  • Solar energy systems
  • Building design
  • Climate studies
  • Astronomy

Acknowledgement

This package is based on the work done by researchers in the field of solar photovoltaics in the packages solposx and pvlib-python. In particular the positioning and refraction methods have been adapted from solposx, while the SPA algorithm and the deltat calculation are ported from pvlib-python. These packages also provide validation data necessary to ensure correctness of the algorithm implementations.

Example Usage

using SolarPosition, Dates, TimeZones# define observer location (latitude, longitude, altitude in meters)obs = Observer(52.35888, 4.88185, 100.0)  # Van Gogh Museum, Amsterdamtz = TimeZone("Europe/Brussels")# a few hours of timestampstimes = collect(DateTime(2023, 6, 21, 10):Hour(1):DateTime(2023, 6, 21, 15));# compute solar positions for all timestampspositions = solar_position(obs, times)
6-element StructArray(::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}) with eltype SolPos{Float64}:
 SolPos(azimuth=136.1908215897483°, elevation=55.13208390808784°, zenith=34.86791609191216°)
 SolPos(azimuth=160.37536557711323°, elevation=59.97408148130664°, zenith=30.025918518693363°)
 SolPos(azimuth=188.39925979964613°, elevation=60.87918930278909°, zenith=29.120810697210917°)
 SolPos(azimuth=214.62987222052016°, elevation=57.493462259962314°, zenith=32.50653774003768°)
 SolPos(azimuth=235.5258846452018°, elevation=50.99264729343901°, zenith=39.00735270656099°)
 SolPos(azimuth=251.77304757136397°, elevation=42.790197455865076°, zenith=47.209802544134924°)

Sunrise and Sunset Calculations

Calculate sunrise, sunset, and solar noon for a specific date with timezone:

result = transit_sunrise_sunset(obs, ZonedDateTime(2023, 6, 21, tz))
TransitSunriseSunset{TimeZones.ZonedDateTime}(TimeZones.ZonedDateTime(2023, 6, 20, 13, 42, 2, tz"Europe/Brussels"), TimeZones.ZonedDateTime(2023, 6, 20, 5, 17, 55, tz"Europe/Brussels"), TimeZones.ZonedDateTime(2023, 6, 20, 22, 6, 10, tz"Europe/Brussels"))

Find the next sunrise from a specific time in UTC:

next_sunrise(obs, DateTime(2023, 6, 21, 12, 30))
2023-06-22T03:18:19

Find the next sunset in UTC:

next_sunset(obs, DateTime(2023, 6, 21, 12, 30))
2023-06-21T20:06:24

Solar positioning algorithms

SolarPosition.jl implements most of the solar position algorithms commonly found in literature and industrial applications. If your preferred algorithm is not listed here, please open an issue or submit a pull request.

AlgorithmReferenceAccuracyDefault Refraction
SPAReda & Andreas, 2004±0.0003°SPARefraction
PSABlanco-Muriel et al.±0.0083°None
WalravenWalraven, 1978±0.0100°None
IqbalIqbal, 1983±0.0100°None
MichalskyMichalsky, 1988±0.0100°MICHALSKY
NOAAGlobal Monitoring Laboratory±0.0167°HUGHES
USNOU.S. Naval Observatory±0.0500°None

Pass an algorithm as the third argument to pick one; the default is PSA.

solar_position(obs, DateTime(2023, 6, 21, 12), Michalsky())
ApparentSolPos(azimuth=188.395988366257°, elevation=60.87786613519506°, zenith=29.122133864804937°,
    apparent_elevation=60.89240853987673°, apparent_zenith=29.107591460123267°

Fast repeated evaluation

For dense time series, the Interpolated wrapper precomputes cubic B-splines of SPA's geocentric solar coordinates and reconstructs positions analytically, roughly 10× faster per query while maintaining SPA's accuracy. Conveniently the same interpolant can be reused for every new observer. It's provided through a package extension for Interpolations.jl:

using Interpolationsalg = Interpolated(SPA(); tspan = (DateTime(2023, 1, 1), DateTime(2024, 1, 1)))solar_position(obs, times, alg)
6-element StructArray(::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}, ::Vector{Float64}) with eltype ApparentSolPos{Float64}:
 ApparentSolPos(azimuth=136.1829378006488°, elevation=55.13005157998651°, zenith=34.86994842001349°,
    apparent_elevation=55.14177749213958°, apparent_zenith=34.85822250786042°
 ApparentSolPos(azimuth=160.36559670569724°, elevation=59.973238963572335°, zenith=30.026761036427665°,
    apparent_elevation=59.98296464815329°, apparent_zenith=30.017035351846708°
 ApparentSolPos(azimuth=188.38915070125674°, elevation=60.87994185528585°, zenith=29.12005814471415°,
    apparent_elevation=60.88931517533394°, apparent_zenith=29.11068482466606°
 ApparentSolPos(azimuth=214.62147404113557°, elevation=57.495598360430634°, zenith=32.504401639569366°,
    apparent_elevation=57.50632074146827°, apparent_zenith=32.49367925853173°
 ApparentSolPos(azimuth=235.5194772517366°, elevation=50.99561782020666°, zenith=39.00438217979334°,
    apparent_elevation=51.00924405162794°, apparent_zenith=38.99075594837206°
 ApparentSolPos(azimuth=251.7679846424769°, elevation=42.79357886962617°, zenith=47.20642113037383°,
    apparent_elevation=42.81173399563851°, apparent_zenith=47.18826600436149°

See the Interpolated Solar Position guide for accuracy figures and when the construction cost pays off.

Automatic differentiation

All algorithms are generic over the number type, so solar positions are differentiable with ForwardDiff.jl out of the box, with no extension package needed:

using ForwardDiffForwardDiff.gradient(    x -> solar_position(Observer(x[1], x[2]), DateTime(2023, 6, 21, 12)).elevation,    [52.35888, 4.88185],)
2-element Vector{Float64}:
 -0.9893110258038672
 -0.0892104084962025

The Automatic Differentiation guide shows gradients through refraction models, panel orientation optimization, and a single axis tracker example.

Uncertainty propagation

The same genericity makes the algorithms work with Measurements.jl, again with no extension package needed:

using Measurementspos = solar_position(Observer(52.35888 ± 0.01, 4.88185 ± 0.01), DateTime(2023, 6, 21, 12))
SolPos(azimuth=188.399 ± 0.019°, elevation=60.8792 ± 0.0099°, zenith=29.1208 ± 0.0099°)

Correlations are tracked, so results that share an input stay consistent. Zenith is derived from elevation, and their sum therefore carries no uncertainty at all:

pos.elevation + pos.zenith

\[90.0 \pm 0.0\]

The Uncertainty Propagation guide covers uncertain refraction parameters and the sunrise and sunset event times, which need transit_sunrise_sunset_seconds to keep their uncertainty.

Numeric precision

The computation runs at the precision of the Observer{T} element type. Float32, Float64, Float128, and BigFloat are supported. A refraction model's own parameter type promotes with the observer's, so build the model at the same precision to keep a narrow result narrow. See the Numeric Precision guide for measured accuracy and runtime of every algorithm at each precision, including multithreaded benchmarks.

Refraction correction algorithms

Atmospheric refraction correction algorithms available in SolarPosition.jl.

AlgorithmReferenceAtmospheric Parameters
HUGHESHughes, 1985Pressure, Temperature
ARCHERArcher, 1980None
BENNETTBennett, 1982Pressure, Temperature
MICHALSKYMichalsky, 1988None
SG2Blanc & Wald, 2012Pressure, Temperature
SPARefractionReda & Andreas, 2004Pressure, Temperature

Extensions

SolarPosition.jl provides optional extensions that are automatically loaded when you have the corresponding package installed.

ExtensionTrigger PackageFeatures
MakieMakie.jlPlotting recipes for solar position visualization
OhMyThreadsOhMyThreads.jlParallel computation of solar positions
ModelingToolkitModelingToolkit.jlSymbolic solar position models for simulations
InterpolationsInterpolations.jlFast Interpolated algorithm construction
TimeZonesTimeZones.jlZonedDateTime input and zoned sunrise/sunset

How to Cite

If you use SolarPosition.jl in your work, please cite using the reference given in CITATION.cff.

Contributing

If you want to make contributions of any kind, please first that a look into our contributing guide directly on GitHub or the contributing page on the website