Refraction Correction

Atmospheric refraction causes the apparent position of the sun to differ from its true geometric position. This effect is most pronounced near the horizon and can be corrected using various atmospheric models.

The correction formula for elevation is:

\[e_{apparent} = e_{true} + R\]

Where:

  • $e_{apparent}$ is the apparent solar elevation angle (degrees)
  • $e_{true}$ is the true solar elevation angle (degrees)
  • $R$ is the refraction correction (degrees), calculated based on the chosen refraction model

Refraction correction comparison Figure 1: Atmospheric refraction causes the sun to appear higher in the sky than its true position, especially near the horizon. Image source: Wikimedia Commons.

SolarPosition.jl includes several refraction correction algorithms. Below is a summary of the available algorithms:

AlgorithmReferenceAtmospheric ParametersStatus
HUGHES[Hug85]Pressure, Temperature
ARCHER[ABO80]None
BENNETT[Ben82]Pressure, Temperature
MICHALSKY[Mic88]None
SG2[BW12]Pressure, Temperature
SPARefraction[RA04]Pressure, Temperature

To calculate refraction, we can use the refraction function:

SolarPosition.Refraction.refractionFunction
refraction(model::RefractionAlgorithm, elevation::T) where {T<:AbstractFloat}

Apply atmospheric refraction correction to the given elevation angle(s).

Arguments

  • model::RefractionAlgorithm: Refraction model to use (e.g., HUGHES())
  • elevation::T: True (unrefracted) solar elevation angle in degrees

Returns

  • Refraction correction in degrees to be added to the elevation angle

Examples

using SolarPosition
hughes = HUGHES(101325.0, 15.0)  # 15°C temperature
elevation = 30.0  # 30 degrees
correction = refraction(hughes, elevation)
apparent_elevation = elevation + correction
source

This function is typically used internally by the solar_position function when a refraction algorithm is specified, but is also a publicly available method.

Info

When using a refraction algorithm like HUGHES, the solar_position function returns an ApparentSolPos struct containing both true and apparent angles.

SolarPosition.Refraction.NoRefractionType
struct NoRefraction <: RefractionAlgorithm

Indicates that no atmospheric refraction correction should be applied.

This is the default refraction setting for solar position calculations. When used, only basic solar position (azimuth, elevation, zenith) is computed.

source
Info

When using NoRefraction (the default), the solar_position function returns a SolPos struct containing only the true geometric angles (azimuth, elevation, zenith). In this case, no refraction correction is applied.

Default refraction model

The DefaultRefraction type is a special marker that indicates to use the default refraction behavior for the selected solar position algorithm. For most algorithms, this means no refraction correction (i.e., equivalent to NoRefraction).

Comparison of Refraction Models

Several different refraction models have been proposed in the literature. SolarPosition.jl only implements a subset of them but PRs are always welcome! To compare the different refraction models, the refraction angle is calculated in the range -1 to 90 degree solar elevation in steps of 0.1 degrees.

using SolarPosition
using CairoMakie

# Define models and elevation range
models = [("Archer", SolarPosition.Refraction.ARCHER()), ("Bennett", SolarPosition.Refraction.BENNETT()),
          ("Hughes", SolarPosition.Refraction.HUGHES()), ("Michalsky", SolarPosition.Refraction.MICHALSKY()),
          ("SG2", SolarPosition.Refraction.SG2()), ("SPA", SolarPosition.Refraction.SPARefraction())]
elevation = -1.5:0.1:90.0

# Create figure with two subplots
fig = Figure(size = (800, 400), backgroundcolor = :transparent, textcolor = "#f5ab35")
ax1 = Axis(fig[1, 1], xlabel = "True elevation [degrees]",
    ylabel = "Refraction correction [degrees]", title = "Near Horizon",
    backgroundcolor = :transparent, xticks = -1:1:4)
ax2 = Axis(fig[1, 2], xlabel = "True elevation [degrees]",
    ylabel = "Refraction correction [degrees]", title = "Full Range (Log Scale)", yscale = log10, backgroundcolor = :transparent)

# Plot refraction for each model
for (name, model) in models
    ref = [SolarPosition.Refraction.refraction(model, e) for e in elevation]
    lines!(ax1, elevation, ref, label = name)
    mask = ref .> 0
    lines!(ax2, elevation[mask], ref[mask])
end

xlims!(ax1, -1.5, 4); ylims!(ax1, 0, 1.0)
xlims!(ax2, -1.5, 90); ylims!(ax2, 1e-3, 1.0)

Legend(fig[0, :], ax1, orientation = :horizontal, framevisible = false,
    tellwidth = false, tellheight = true, nbanks = 1)
fig
Example block output

A comparison of the refraction models is visualized above. The plot on the left shows refraction for solar elevation angles near sunrise/sunset, where refraction is most significant. The plot on the right shows the refraction angles for the entire range of solar elevation angles. Note that for the right plot, the y-axis is a log scale, which emphasizes the difference between the models.

Hughes

The Hughes refraction model accounts for atmospheric pressure and temperature effects.

This model was developed by [Hug85] and is used in the SUNAEP software [Zim81]. It's also the basis for the refraction correction in NOAA's solar position calculator (using fixed pressure of 101325 Pa and temperature of 10°C).

SolarPosition.Refraction.HUGHESType
struct HUGHES{T} <: RefractionAlgorithm

Hughes refraction model.

This function was developed by G. Hughes [1] and was used in the SUNAEP software [2].

It is also used to calculate the refraction correction in the NOAA solar position algorithm using a fixed pressure of 101325 Pa and a temperature of 10 degrees Celsius.

Fields

  • pressure::Any: Annual average atmospheric pressure [Pascal]

  • temperature::Any: Annual average temperature [°C]

Constructor

  • HUGHES(): Uses default parameters: pressure = 101325 Pa, temperature = 12 °C
  • HUGHES(pressure, temperature): Specify custom pressure [Pa] and temperature [°C]

Notes

The equation to calculate the refraction correction is given by:

For 5° < elevation ≤ 90°:

\[\frac{58.1}{\tan(el)} - \frac{0.07}{\tan(el)^3} + \frac{8.6 \times 10^{-5}}{\tan(el)^5}\]

For -0.575° < elevation ≤ 5°:

\[el \cdot (-518.2 + el \cdot (103.4 + el \cdot (-12.79 + el \cdot 0.711))) + 1735\]

For elevation ≤ -0.575°:

\[\frac{-20.774}{\tan(el)}\]

where el is the true (unrefracted) solar elevation angle.

The result is then corrected for temperature and pressure:

\[\text{Refract} \times \frac{283}{273 + T} \times \frac{P}{101325} \times \frac{1}{3600}\]

Literature

This function was developed by [Hug85] and was used in the SUNAEP software [Zim81]. It is also used to calculate the refraction correction in the NOAA solar position algorithm using a fixed pressure of 101325 Pa and a temperature of 10 degrees Celsius.

Example

using SolarPosition

# Create Hughes refraction model with default parameters
hughes = HUGHES()

# Or specify custom atmospheric conditions
hughes_custom = HUGHES(101325.0, 25.0)  # 25°C temperature

# Apply refraction correction to elevation angle
elevation = 30.0  # degrees
refraction_correction = refraction(hughes, elevation)
apparent_elevation = elevation + refraction_correction
source

Archer

The Archer refraction model is a cosine-based correction that does not require atmospheric parameters.

This simplified model from [ABO80] computes refraction based on the zenith angle using trigonometric relationships. It's useful when atmospheric data is not available.

SolarPosition.Refraction.ARCHERType
struct ARCHER <: RefractionAlgorithm

Archer refraction model.

Atmospheric refraction correction based on the Archer algorithm.

This function calculates the atmospheric refraction correction of the solar elevation angle using the method described by Archer [1]. The method was originally developed to be used with the Walraven solar position algorithm [2].

Fields

Constructor

  • ARCHER(): Creates an Archer refraction model instance

Notes

The equation to calculate the refraction correction is given by:

\[\begin{aligned} C &= \cos(Z) + 0.0083 \cdot \left(\frac{1}{0.955 + (20.267 \cdot \cos(Z))} - 0.047121 \right)\\ Z_a &= \arccos(C)\\ \text{refraction} &= Z - Z_a \end{aligned}\]

where $Z$ is the true solar zenith angle and $Z_a$ is the apparent zenith angle.

Literature

This method was described by [ABO80] and was originally developed to be used with the Walraven solar position algorithm [Wal78].

Example

using SolarPosition

# Create Archer refraction model
archer = ARCHER()

# Apply refraction correction to elevation angle
elevation = 30.0  # degrees
refraction_correction = refraction(archer, elevation)
apparent_elevation = elevation + refraction_correction
source

Bennett

The Bennett refraction model is widely used in marine navigation and accounts for atmospheric conditions.

Developed by [Ben82], this model provides accurate refraction corrections with adjustments for atmospheric pressure and temperature. It's particularly effective for low elevation angles.

SolarPosition.Refraction.BENNETTType
struct BENNETT{T} <: RefractionAlgorithm

Bennett refraction model.

Atmospheric refraction correction based on the Bennett algorithm.

Calculation of atmospheric refraction correction of the solar elevation angle using the method developed by Bennett [1].

Fields

  • pressure::Any: Annual average atmospheric pressure [Pascal]

  • temperature::Any: Annual average temperature [°C]

Constructor

  • BENNETT(): Uses default parameters: pressure = 101325 Pa, temperature = 12 °C
  • BENNETT(pressure, temperature): Specify custom pressure [Pa] and temperature [°C]

Notes

The equation to calculate the refraction correction is given by:

\[\text{ref} = \frac{0.28 \cdot P}{T+273} \cdot \frac{0.016667}{\tan(el + 7.31 / (el+4.4))}\]

where $P$ is the local air pressure in hPa, $T$ is the local air temperature in °C, and $el$ is the true (uncorrected) solar elevation angle.

Literature

This method was described by [Ben82].

Example

using SolarPosition

# Create Bennett refraction model with default parameters
bennett = BENNETT()

# Or specify custom atmospheric conditions
bennett_custom = BENNETT(101325.0, 25.0)  # 25°C temperature

# Apply refraction correction to elevation angle
elevation = 30.0  # degrees
refraction_correction = refraction(bennett, elevation)
apparent_elevation = elevation + refraction_correction
source

Michalsky

The Michalsky refraction model uses a rational polynomial approximation.

From [Mic88], this algorithm is part of the Astronomical Almanac's method for approximate solar position calculations. It includes special handling for very low elevation angles.

SolarPosition.Refraction.MICHALSKYType
struct MICHALSKY <: RefractionAlgorithm

Michalsky refraction model.

Atmospheric refraction correction based on the Michalsky algorithm.

This function calculates the atmospheric refraction correction of the solar elevation angle using the method described by Michalsky [1].

Fields

Constructor

  • MICHALSKY(): Creates a Michalsky refraction model instance

Notes

The equation to calculate the refraction correction is given by:

\[\text{ref} = \frac{3.51561 \cdot (0.1594 + 0.0196 \cdot el + 0.00002 \cdot el^2)}{1 + 0.505 \cdot el + 0.0845 \cdot el^2}\]

where $el$ is the true (uncorrected) solar elevation angle.

Note that 3.51561 = 1013.2 mb / 288.2 °C.

For elevation angles below -0.56°, the refraction correction is clamped to 0.56°.

Literature

This method was described by [Mic88].

Example

using SolarPosition

# Create Michalsky refraction model
michalsky = MICHALSKY()

# Apply refraction correction to elevation angle
elevation = 30.0  # degrees
refraction_correction = refraction(michalsky, elevation)
apparent_elevation = elevation + refraction_correction
source

SG2

The SG2 (Second Generation) refraction algorithm is optimized for fast computation over multi-decadal periods.

Developed by [BW12], this algorithm uses a two-regime approach with different formulas for elevations above and below a threshold. It accounts for atmospheric pressure and temperature.

SolarPosition.Refraction.SG2Type
struct SG2{T} <: RefractionAlgorithm

SG2 refraction model.

Atmospheric refraction correction based on the algorithm in SG2.

This function calculates the atmospheric refraction correction of the solar elevation angle using the method developed by Ph. Blanc and L. Wald [1].

Fields

  • pressure::Any: Annual average atmospheric pressure [Pascal]

  • temperature::Any: Annual average temperature [°C]

Constructor

  • SG2(): Uses default parameters: pressure = 101325 Pa, temperature = 12 °C
  • SG2(pressure, temperature): Specify custom pressure [Pa] and temperature [°C]

Notes

The equation to calculate the refraction correction is given by:

For $el > -0.01$ radians:

\[\frac{P}{1010} \cdot \frac{283}{273+T} \cdot \frac{2.96706 \times 10^{-4}}{\tan(el+0.0031376 \cdot (el+0.089186)^{-1})}\]

For $el \leq -0.01$ radians:

\[-\frac{P}{1010} \cdot \frac{283}{273+T} \cdot \frac{1.005516 \times 10^{-4}}{\tan(el)}\]

where $el$ is the true solar elevation angle, $P$ is the local air pressure in hPa, and $T$ is the local air temperature in °C.

Literature

This method was described by [BW12].

Example

using SolarPosition

# Create SG2 refraction model with default parameters
sg2 = SG2()

# Or specify custom atmospheric conditions
sg2_custom = SG2(101325.0, 25.0)  # 25°C temperature

# Apply refraction correction to elevation angle
elevation = 30.0  # degrees
refraction_correction = refraction(sg2, elevation)
apparent_elevation = elevation + refraction_correction
source

SPARefraction

The SPARefraction (Solar Position Algorithm) refraction model is part of NREL's high-accuracy solar position algorithm.

From [RA04], this is the refraction correction used in NREL's SPA algorithm, which is accurate to ±0.0003° over the years -2000 to 6000. It includes a configurable refraction limit for below-horizon calculations.

SolarPosition.Refraction.SPARefractionType
struct SPARefraction{T} <: RefractionAlgorithm

SPARefraction - SPA (Solar Position Algorithm) refraction model.

Atmospheric refraction correction from the SPA algorithm.

This function calculates the atmospheric refraction correction of the solar elevation angle using the method described in Reda and Andreas's [1] Solar Position Algorithm (SPA).

Fields

  • pressure::Any: Annual average atmospheric pressure [Pascal]

  • temperature::Any: Annual average temperature [°C]

  • refraction_limit::Any: Minimum elevation angle for refraction correction [degrees]

Constructor

  • SPARefraction(): Uses default parameters: pressure = 101325 Pa, temperature = 12 °C, refraction_limit = -0.5667°
  • SPARefraction(pressure, temperature): Specify custom pressure [Pa] and temperature [°C], uses default refraction_limit
  • SPARefraction(pressure, temperature, refraction_limit): Also specify refraction limit [degrees]

Notes

The equation to calculate the refraction correction is given by:

\[\text{ref} = \frac{P}{1010} \cdot \frac{283}{273 + T} \cdot \frac{1.02}{60 \cdot \tan(el + 10.3/(el + 5.11))}\]

where $el$ is the true solar elevation angle, $P$ is the annual average local air pressure in hPa/mbar, and $T$ is the annual average local air temperature in °C.

The refraction limit parameter determines the solar elevation angle below which refraction is not applied, as the sun is assumed to be below horizon. Note that the sun diameter (0.26667°) is added to this limit.

Literature

This method was described by [RA04].

Example

using SolarPosition

# Create SPARefraction model with default parameters
spa = SPARefraction()

# Or specify custom atmospheric conditions
spa_custom = SPARefraction(101325.0, 25.0)  # 25°C temperature

# With custom refraction limit
spa_limit = SPARefraction(101325.0, 12.0, -1.0)  # Don't correct below -1°

# Apply refraction correction to elevation angle
elevation = 30.0  # degrees
refraction_correction = refraction(spa, elevation)
apparent_elevation = elevation + refraction_correction
source