Skip to content

Commit

Permalink
Twilight events dedicated class
Browse files Browse the repository at this point in the history
In the same way `Astronoby::Events::ObservationEvents` represent
observation events for any body on the celestial sphere, twilight events
exist but are related only to the Sun. For example, it doesn't make
sense to talk about twilight related to a planet or a distant star.

In a goal of making events more centralized, twilight times are now
extract from `Astronoby::Sun` into their own dedicated
`Astronoby::Events::TwilightEvents` class.

The same methods are exposed. This adds natural memoization as all the
times are calculated when the object initialized, which is when the
`Astronoby::Sun#twilight_events` is called.
  • Loading branch information
rhannequin committed Apr 15, 2024
1 parent 429c326 commit 736ad12
Show file tree
Hide file tree
Showing 6 changed files with 502 additions and 375 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,36 @@ observation_events.setting_azimuth.str(:dms)
# => "+250° 23′ 33.6177″"
```

### Twilight times

```rb
epoch = Astronoby::Epoch.from_time(Date.new(2024, 1, 1))
sun = Astronoby::Sun.new(epoch: epoch)
observer = Astronoby::Observer.new(
latitude: Astronoby::Angle.from_degrees(48.8566),
longitude: Astronoby::Angle.from_degrees(2.3522)
)
twilight_events = sun.twilight_events(observer: observer)

twilight_events.morning_astronomical_twilight_time
# => 2024-01-01 05:47:24 UTC

twilight_events.morning_nautical_twilight_time
# => 2024-01-01 06:25:41 UTC

twilight_events.morning_civil_twilight_time
# => 2024-01-01 07:05:51 UTC

twilight_events.evening_civil_twilight_time
# => 2024-01-01 16:37:37 UTC

twilight_events.evening_nautical_twilight_time
# => 2024-01-01 17:17:46 UTC

twilight_events.evening_astronomical_twilight_time
# => 2024-01-01 17:56:03 UTC
```

### Solstice and Equinox times

```rb
Expand Down
1 change: 1 addition & 0 deletions lib/astronoby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require "astronoby/equinox_solstice"
require "astronoby/errors"
require "astronoby/events/observation_events"
require "astronoby/events/twilight_events"
require "astronoby/geocentric_parallax"
require "astronoby/mean_obliquity"
require "astronoby/nutation"
Expand Down
101 changes: 6 additions & 95 deletions lib/astronoby/bodies/sun.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Sun
EVENING = :evening
].freeze

attr_reader :epoch

# Source:
# Title: Practical Astronomy with your Calculator or Spreadsheet
# Authors: Peter Duffett-Smith and Jonathan Zwart
Expand Down Expand Up @@ -120,40 +122,10 @@ def observation_events(observer:)
)
end

# @param observer [Astronoby::Observer] Observer of the event
# @return [Time, nil] Time the morning civil twilight starts
def morning_civil_twilight_time(observer:)
twilight_time(CIVIL, MORNING, observer)
end

# @param observer [Astronoby::Observer] Observer of the event
# @return [Time, nil] Time the evening civil twilight ends
def evening_civil_twilight_time(observer:)
twilight_time(CIVIL, EVENING, observer)
end

# @param observer [Astronoby::Observer] Observer of the event
# @return [Time, nil] Time the morning nautical twilight starts
def morning_nautical_twilight_time(observer:)
twilight_time(NAUTICAL, MORNING, observer)
end

# @param observer [Astronoby::Observer] Observer of the event
# @return [Time, nil] Time the evening nautical twilight ends
def evening_nautical_twilight_time(observer:)
twilight_time(NAUTICAL, EVENING, observer)
end

# @param observer [Astronoby::Observer] Observer of the event
# @return [Time, nil] Time the morning astronomical twilight starts
def morning_astronomical_twilight_time(observer:)
twilight_time(ASTRONOMICAL, MORNING, observer)
end

# @param observer [Astronoby::Observer] Observer of the event
# @return [Time, nil] Time the evening astronomical twilight ends
def evening_astronomical_twilight_time(observer:)
twilight_time(ASTRONOMICAL, EVENING, observer)
# @param observer [Astronoby::Observer] Observer of the events
# @return [Astronoby::Events::TwilightEvents] Sun's twilight events
def twilight_events(observer:)
Events::TwilightEvents.new(sun: self, observer: observer)
end

# @return [Numeric] Earth-Sun distance in meters
Expand Down Expand Up @@ -232,66 +204,5 @@ def distance_angular_size_factor

term1 / term2
end

def current_date
Epoch.to_utc(@epoch).to_date
end

# Source:
# Title: Practical Astronomy with your Calculator or Spreadsheet
# Authors: Peter Duffett-Smith and Jonathan Zwart
# Edition: Cambridge University Press
# Chapter: 50 - Twilight
def twilight_time(twilight, period_of_the_day, observer)
observation_events = observation_events(observer: observer)
period_time = if period_of_the_day == MORNING
observation_events.rising_time
else
observation_events.setting_time
end

hour_angle_at_period = equatorial_coordinates_at_midday
.compute_hour_angle(time: period_time, longitude: observer.longitude)

zenith_angle = TWILIGHT_ANGLES[twilight]

term1 = zenith_angle.cos -
observer.latitude.sin *
equatorial_coordinates_at_midday.declination.sin
term2 = observer.latitude.cos *
equatorial_coordinates_at_midday.declination.cos
hour_angle_ratio_at_twilight = term1 / term2
return nil unless hour_angle_ratio_at_twilight.between?(-1, 1)

hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight)
time_sign = -1

if period_of_the_day == MORNING
hour_angle_at_twilight =
Angle.from_degrees(360 - hour_angle_at_twilight.degrees)
time_sign = 1
end

twilight_in_hours =
time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours *
GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE
twilight_in_seconds = time_sign * twilight_in_hours * 3600
(period_time + twilight_in_seconds).round
end

def midday
utc_from_epoch = Epoch.to_utc(@epoch)
Time.utc(
utc_from_epoch.year,
utc_from_epoch.month,
utc_from_epoch.day,
12
)
end

def equatorial_coordinates_at_midday
apparent_ecliptic_coordinates
.to_apparent_equatorial(epoch: Epoch.from_time(midday))
end
end
end
114 changes: 114 additions & 0 deletions lib/astronoby/events/twilight_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# frozen_string_literal: true

module Astronoby
module Events
class TwilightEvents
TWILIGHTS = [
CIVIL = :civil,
NAUTICAL = :nautical,
ASTRONOMICAL = :astronomical
].freeze

TWILIGHT_ANGLES = {
CIVIL => Angle.from_degrees(96),
NAUTICAL => Angle.from_degrees(102),
ASTRONOMICAL => Angle.from_degrees(108)
}.freeze

PERIODS_OF_THE_DAY = [
MORNING = :morning,
EVENING = :evening
].freeze

attr_reader :morning_civil_twilight_time,
:evening_civil_twilight_time,
:morning_nautical_twilight_time,
:evening_nautical_twilight_time,
:morning_astronomical_twilight_time,
:evening_astronomical_twilight_time

def initialize(observer:, sun:)
@observer = observer
@sun = sun
PERIODS_OF_THE_DAY.each do |period_of_the_day|
TWILIGHT_ANGLES.each do |twilight, _|
compute(period_of_the_day, twilight)
end
end
end

private

# Source:
# Title: Practical Astronomy with your Calculator or Spreadsheet
# Authors: Peter Duffett-Smith and Jonathan Zwart
# Edition: Cambridge University Press
# Chapter: 50 - Twilight
def compute(period_of_the_day, twilight)
period_time = if period_of_the_day == MORNING
observation_events.rising_time
else
observation_events.setting_time
end

hour_angle_at_period = equatorial_coordinates_at_midday
.compute_hour_angle(time: period_time, longitude: @observer.longitude)

zenith_angle = TWILIGHT_ANGLES[twilight]

term1 = zenith_angle.cos -
@observer.latitude.sin *
@equatorial_coordinates_at_midday.declination.sin
term2 = @observer.latitude.cos *
equatorial_coordinates_at_midday.declination.cos
hour_angle_ratio_at_twilight = term1 / term2

unless hour_angle_ratio_at_twilight.between?(-1, 1)
return instance_variable_set(
:"@#{period_of_the_day}_#{twilight}_twilight_time",
nil
)
end

hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight)
time_sign = -1

if period_of_the_day == MORNING
hour_angle_at_twilight =
Angle.from_degrees(360 - hour_angle_at_twilight.degrees)
time_sign = 1
end

twilight_in_hours =
time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours *
GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE
twilight_in_seconds = time_sign * twilight_in_hours * 3600

instance_variable_set(
:"@#{period_of_the_day}_#{twilight}_twilight_time",
(period_time + twilight_in_seconds).round
)
end

def observation_events
@observation_events ||= @sun.observation_events(observer: @observer)
end

def midday
utc_from_epoch = Epoch.to_utc(@sun.epoch)
Time.utc(
utc_from_epoch.year,
utc_from_epoch.month,
utc_from_epoch.day,
12
)
end

def equatorial_coordinates_at_midday
@equatorial_coordinates_at_midday ||=
@sun.apparent_ecliptic_coordinates
.to_apparent_equatorial(epoch: Epoch.from_time(midday))
end
end
end
end

0 comments on commit 736ad12

Please sign in to comment.