Skip to content

Commit 9ccff10

Browse files
committed
Finish PSA algorithm
1 parent 4b96243 commit 9ccff10

4 files changed

Lines changed: 57 additions & 53 deletions

File tree

src/Positioning/Positioning.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,20 @@ Angles are in radians.
5555
"""
5656
function solar_position(
5757
obs::Observer{T},
58-
dt::ZonedDateTime;
58+
dt::DateTime;
5959
alg::SolarAlgorithm = PSA(),
6060
kwargs...,
6161
) where {T}
62-
Positioning.solar_position(obs, dt, alg; kwargs...)
62+
_solar_position(obs, dt, alg; kwargs...)
6363
end
6464

65-
"""
66-
solar_position(obs, alg, t, opts, algopts) -> SolarPos
67-
68-
Internal dispatch function for solar position calculation.
69-
"""
70-
function solar_position(obs, dt::ZonedDateTime, alg::SolarAlgorithm; kwargs...)
71-
_solar_position(obs, dt, alg; kwargs...)
65+
function solar_position(
66+
obs::Observer{T},
67+
dt::ZonedDateTime;
68+
alg::SolarAlgorithm = PSA(),
69+
kwargs...,
70+
) where {T}
71+
_solar_position(obs, DateTime(dt, UTC), alg; kwargs...)
7272
end
7373

7474
export NOAA, PSA, Observer, solar_position

src/Positioning/psa.jl

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
"""
22
PSA
33
4-
Solar position algorithm based on PSA's implementation.
4+
Solar position algorithm based on PSA's implementation [1].
5+
6+
[1] M. Blanco, D. Alarcón, T. López, and M. Lara, "Computing the Solar
7+
Vector," Solar Energy, vol. 70, no. 5, 2001,
8+
:doi:`10.1016/S0038-092X(00)00156-0`
9+
[2] M. Blanco, K. Milidonis, and A. Bonanos, "Updating the PSA sun
10+
position algorithm," Solar Energy, vol. 212, 2020,
11+
:doi:`10.1016/j.solener.2020.10.084`
512
"""
613

714
struct PSA <: SolarAlgorithm end
@@ -46,53 +53,48 @@ const PSA_PARAMS = Dict{Int,SVector{15,Float64}}(
4653
"""
4754
_solar_position(
4855
obs::Observer{T},
49-
dt::ZonedDateTime,
56+
dt::DateTime,
5057
::PSA,
5158
) -> SolarPos{T}
5259
PSA algorithm implementation stub.
5360
"""
5461
function _solar_position(
5562
obs::Observer{T},
56-
dt::ZonedDateTime,
63+
dt::DateTime,
5764
::PSA;
5865
coeffs::Int = 2020,
5966
) where {T}
60-
6167
p = PSA_PARAMS[coeffs]
6268

63-
phi = obs.latitude_rad
64-
lambda_t = obs.longitude_rad
65-
66-
# extract date components
67-
h = fractional_hour(dt)
68-
69-
# julian day calculation
69+
# elapsed julian days (n) since J2000.0
7070
jd = Dates.datetime2julian(dt)
71-
n = jd - 2451545.0
72-
73-
# ecliptic longitude and obliquity
74-
omega = p[1] + p[2] * n
75-
L = p[3] + p[4] * n
76-
g = p[5] + p[6] * n
77-
lambda_e = L + p[7] * sin(g) + p[8] * sin(2 * g) + p[9] + p[10] * sin(omega)
78-
epsilon = p[11] + p[12] * n + p[13] * cos(omega)
71+
n = jd - 2451545.0 # Eq. 2
7972

80-
# right ascension and declination
81-
ra = atan(cos(epsilon) * sin(lambda_e), cos(lambda_e)) % (2 * pi)
82-
δ = asin(sin(epsilon) * sin(lambda_e))
73+
# ecliptic coordinates of the sun
74+
# ecliptic longitude (λₑ), and obliquity of the ecliptic (ϵ)
75+
Ω = p[1] + p[2] * n # Eq. 3
76+
L = p[3] + p[4] * n # Eq. 4
77+
g = p[5] + p[6] * n # Eq. 5
78+
λₑ = L + p[7] * sin(g) + p[8] * sin(2 * g) + p[9] + p[10] * sin(Ω) # Eq. 6
79+
ϵ = p[11] + p[12] * n + p[13] * cos(Ω) # Eq. 7
8380

84-
# local coordinates
85-
gmst = p[14] + p[15] * n + h
86-
lmst = gmst + lambda_t # check units if gmst in hours
87-
w = lmst - ra
81+
# celestial right ascension (ra) and declination (d)
82+
ra = atan(cos(ϵ) * sin(λₑ), cos(λₑ)) # Eq. 8
83+
ra = mod(ra, 2π)
84+
δ = asin(sin(ϵ) * sin(λₑ)) # Eq. 9
8885

89-
theta_z = acos(cos(phi) * cos(w) * cos(δ) + sin(δ) * sin(phi))
90-
gamma = atan(-sin(w), (tan(δ) * cos(phi) - sin(phi) * cos(w)))
86+
# computes the local coordinates: azimuth (γ) and zenith angle (θz)
87+
ϕ = obs.latitude_rad
88+
hour = fractional_hour(dt)
89+
gmst = p[14] + p[15] * n + hour # Eq. 10
90+
λt = rad2deg(obs.longitude_rad)
91+
lmst = (gmst * 15 + λt) * π / 180 # Eq. 11
92+
ω = lmst - ra # Eq. 12
93+
θz = acos(cos(ϕ) * cos(ω) * cos(δ) + sin(δ) * sin(ϕ)) # Eq. 13
94+
γ = atan(-sin(ω), (tan(δ) * cos(ϕ) - sin(ϕ) * cos(ω))) # Eq. 14
9195

92-
# Earth mean radius correction
93-
EMR = 6371.01
94-
AU = 149597890
95-
theta_z += (EMR / AU) * sin(theta_z)
96+
# parallax correction
97+
θz = θz + (EMR / AU) * sin(θz) # Eq. 15,16
9698

97-
return SolarPos{T}(pi / 2 - theta_z, theta_z, gamma) # elevation, zenith, azimuth in radians
99+
return SolarPos(mod(rad2deg(γ), 360), rad2deg/ 2 - θz), rad2deg(θz))
98100
end

src/Positioning/utils.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ deg2rad(x::Real) = float(x) * (π / 180)
88
rad2deg(x::Real) = float(x) * (180 / π)
99

1010
# fractional hour helper
11-
fractional_hour(dt::ZonedDateTime) = hour(dt) + minute(dt) / 60 + second(dt) / 3600
11+
fractional_hour(dt::DateTime) = hour(dt) + minute(dt) / 60 + second(dt) / 3600
12+
13+
# constants
14+
EMR = 6371.01 # Earth Mean Radius in km
15+
AU = 149597890 # Astronomical Unit in km

test/test-psa.jl

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,30 +61,28 @@ end
6161
@testset "PSA" begin
6262
coeffs = Dict(2020 => expected_2020, 2001 => expected_2001)
6363

64-
@testset "Coeff $i" for (i, f) in coeffs
65-
df_expected = f()
64+
@testset "Coeff $i" for (i, expected) in coeffs
65+
df_expected = expected()
6666
conds = test_conditions()
6767
@test size(df_expected, 1) == 19
6868
@test size(df_expected, 2) == 3
6969
@test size(conds, 1) == 19
7070
@test size(conds, 2) == 4
7171

7272
# conds = time, latitude, longitude, altitude
73-
for (dt, lat, lon, alt) in eachrow(conds)
73+
# for (dt, lat, lon, alt) in eachrow(conds)
74+
for ((dt, lat, lon, alt), (exp_elev, exp_zen, exp_az)) in
75+
zip(eachrow(conds), eachrow(df_expected))
7476
if ismissing(alt)
7577
obs = Observer(lat, lon)
7678
else
7779
obs = Observer(lat, lon, altitude = alt)
7880
end
7981

8082
res = solar_position(obs, dt; alg = PSA(), coeffs = i)
81-
# @test isapprox(
82-
# rad2deg(res.elevation),
83-
# df_expected.elevation[row.row],
84-
# atol = 1e-4,
85-
# )
86-
# @test isapprox(rad2deg(res.zenith), df_expected.zenith[row.row], atol = 1e-4)
87-
# @test isapprox(rad2deg(res.azimuth), df_expected.azimuth[row.row], atol = 1e-4)
83+
@test isapprox(res.elevation, exp_elev, atol = 1e-4)
84+
@test isapprox(res.zenith, exp_zen, atol = 1e-4)
85+
@test isapprox(res.azimuth, exp_az, atol = 1e-4)
8886
end
8987
end
9088
end

0 commit comments

Comments
 (0)