defmodule Azan.SolarTime do
@moduledoc """
Documentation for `SolarTime`.
"""
alias Azan.{
Astronomical,
AstronomyUtility,
CalculationParameter,
Coordinate,
PolarCircleResolution,
SolarCoordinate
}
use TypedStruct
typedstruct do
field :date, %Date{}
field :coordinate, %Coordinate{}
field :observer, %Coordinate{}
field :solar, %SolarCoordinate{}
field :prev_solar, %SolarCoordinate{}
field :next_solar, %SolarCoordinate{}
field :approx_transit, float()
field :transit, float()
field :sunrise, float()
field :sunset, float()
end
def new(%DateTime{} = datetime, coordinate) do
datetime |> DateTime.to_date() |> new(coordinate)
end
def new(
%Date{year: year, month: month, day: day},
%Coordinate{longitude: longitude} = coordinate
) do
julian_day = Astronomical.julian_day(year, month, day)
solar =
%SolarCoordinate{
apparent_side_real_time: apparent_side_real_time,
right_ascension: right_ascension
} = SolarCoordinate.init_by_julian_day(julian_day)
prev_solar = SolarCoordinate.init_by_julian_day(julian_day - 1)
next_solar = SolarCoordinate.init_by_julian_day(julian_day + 1)
solar_altitude = -50.0 / 60.0
m0 = Astronomical.approximate_transit(longitude, apparent_side_real_time, right_ascension)
%__MODULE__{
observer: coordinate,
solar: solar,
prev_solar: prev_solar,
next_solar: next_solar,
approx_transit: m0,
transit:
Astronomical.corrected_transit(
m0,
longitude,
apparent_side_real_time,
right_ascension,
prev_solar.right_ascension,
next_solar.right_ascension
),
sunrise:
AstronomyUtility.corrected_hour_angle(
m0,
solar_altitude,
coordinate,
false,
apparent_side_real_time,
solar,
prev_solar,
next_solar
),
sunset:
AstronomyUtility.corrected_hour_angle(
m0,
solar_altitude,
coordinate,
true,
apparent_side_real_time,
solar,
prev_solar,
next_solar
)
}
end
@spec find_pair_solar_time(Date.t(), Coordinate.t(), CalculationParameter.t()) ::
{:ok, {__MODULE__.t(), __MODULE__.t()}}
def find_pair_solar_time(
%Date{} = date,
%Coordinate{} = coordinate,
%CalculationParameter{polar_circle_resolution: polar_circle_resolution}
) do
tomorrow = date |> Timex.shift(days: 1)
pair =
new(date, coordinate)
|> resolve_safe_time(new(tomorrow, coordinate), polar_circle_resolution, date, coordinate)
{:ok, pair}
end
def hour_angle(
%__MODULE__{
approx_transit: approx_transit,
observer: observer,
solar:
%SolarCoordinate{
apparent_side_real_time: apparent_side_real_time
} = solar,
prev_solar: prev_solar,
next_solar: next_solar
},
angle,
after_transit
) do
AstronomyUtility.corrected_hour_angle(
approx_transit,
angle,
observer,
after_transit,
apparent_side_real_time,
solar,
prev_solar,
next_solar
)
end
def afternoon(
%__MODULE__{
observer: %Coordinate{latitude: latitude},
solar: %SolarCoordinate{declination: declination}
} = solar_time,
shadow_length
) do
tangent = abs(latitude - declination)
inverse = shadow_length + Math.tan(Math.deg2rad(tangent))
angle = (1.0 / inverse) |> Math.atan() |> Math.rad2deg()
solar_time |> hour_angle(angle, true)
end
def resolve_safe_time(
%__MODULE__{sunset: sunset, sunrise: sunrise},
%__MODULE__{sunrise: tomorrow_sunrise},
polar_circle_resolution,
date,
coordinate
)
when not is_number(sunrise) or not is_number(sunset) or
(not is_number(tomorrow_sunrise) and polar_circle_resolution !== :unresolved) do
%{solar_time: solar_time, tomorrow_solar_time: tomorrow_solar_time} =
polar_circle_resolution
|> PolarCircleResolution.polar_circle_resolved_values(date, coordinate)
{solar_time, tomorrow_solar_time}
end
def resolve_safe_time(
solar_time,
tomorrow_solar_time,
_polar_circle_resolution,
_date,
_coordinate
),
do: {solar_time, tomorrow_solar_time}
end