Class: SunCalc

Inherits:
Object
  • Object
show all
Extended by:
Helpers
Defined in:
lib/sun_calc.rb,
lib/sun_calc/helpers.rb,
lib/sun_calc/version.rb,
lib/sun_calc/constants.rb

Overview

:nodoc:

Defined Under Namespace

Modules: Helpers

Constant Summary collapse

DEFAULT_SUN_TIMES =

Sun times configuration (angle, morning name, evening name).

[
  [-0.833, 'sunrise', 'sunset'],
  [-0.3, 'sunrise_end', 'sunset_start'],
  [-6, 'dawn', 'dusk'],
  [-12, 'nautical_dawn', 'nautical_dusk'],
  [-18, 'night_end', 'night_start'],
  [6, 'golden_hour_end', 'golden_hour_start']
].freeze
VERSION =
'0.1.0'.freeze
ONE_RADIAN =
Math::PI / 180
ONE_DAY_IN_SECONDS =
60 * 60 * 24
J0 =
0.0009
J1970 =
2_440_588
J2000 =
2_451_545
OBLIQUITY_OF_THE_EARTH =
ONE_RADIAN * 23.4397

Class Method Summary collapse

Methods included from Helpers

altitude, approx_transit, astro_refraction, azimuth, declination, ecliptic_longitude, from_julian, get_set_j, hour_angle, hours_later, julian_cycle, moon_coords, right_ascension, sidereal_time, solar_mean_anomaly, solar_transit_j, sun_coords, to_days, to_julian

Class Method Details

.add_sun_time(angle, rise_name, set_name) ⇒ Object

Adds a custom time to the times configuration.



67
68
69
# File 'lib/sun_calc.rb', line 67

def self.add_sun_time(angle, rise_name, set_name)
  @__sun_times__.push([angle, rise_name, set_name])
end

.moon_illumination(date = Time.now) ⇒ Object

Calculates illumination parameters for the moon for a given date.

Formulas are based on:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/sun_calc.rb', line 158

def self.moon_illumination(date = Time.now)
  d = to_days(date)
  s = sun_coords(d)
  m = moon_coords(d)
  sdist = 149_598_000 # Distance from Earth to Sun in kilometers
  phi = Math.acos(Math.sin(s[:dec]) * Math.sin(m[:dec]) +
             Math.cos(s[:dec]) * Math.cos(m[:dec]) * Math.cos(s[:ra] -
                                                              m[:ra]))
  inc = Math.atan2(sdist * Math.sin(phi), m[:dist] - sdist * Math.cos(phi))
  angle = Math.atan2(Math.cos(s[:dec]) * Math.sin(s[:ra] - m[:ra]),
                     Math.sin(s[:dec]) * Math.cos(m[:dec]) -
                       Math.cos(s[:dec]) * Math.sin(m[:dec]) *
                         Math.cos(s[:ra] - m[:ra]))
  { fraction: (1 + Math.cos(inc)) / 2,
    phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math::PI,
    angle: angle }
end

.moon_position(date, lat, lng) ⇒ Object

Calculates moon position for a gived date, latitude, and longitude.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/sun_calc.rb', line 72

def self.moon_position(date, lat, lng)
  lw  = ONE_RADIAN * -lng
  phi = ONE_RADIAN * lat
  d   = to_days(date)
  c   = moon_coords(d)
  h_  = sidereal_time(d, lw) - c[:ra]
  h   = altitude(h_, phi, c[:dec])
  # Formula 14.1 from "Astronomical Algorithms" 2nd edition by Jean Meeus
  # (Willmann-Bell, Richmond) 1998.
  pa  = Math.atan2(Math.sin(h_),
                   Math.tan(phi) * Math.cos(c[:dec]) -
                     Math.sin(c[:dec]) * Math.cos(h_))
  h += astro_refraction(h) # altitude correction for refraction

  { azimuth: azimuth(h_, phi, c[:dec]),
    altitude: h,
    distance: c[:dist],
    parallacticAngle: pa }
end

.moon_times(date, lat, lng) ⇒ Object

Calculates moon times for a given date, latitude, and longitude.

Calculations for moon rise and set times are based on:



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/sun_calc.rb', line 96

def self.moon_times(date, lat, lng)
  t = Time.utc(date.year, date.month, date.day)
  hc = 0.133 * ONE_RADIAN
  h0 = SunCalc.moon_position(t, lat, lng)[:altitude] - hc
  ye = 0
  max = nil
  min = nil
  rise = nil
  set = nil
  # Iterate in 2-hour steps checking if a 3-point quadratic curve crosses zero
  # (which means rise or set). Assumes x values -1, 0, +1.
  (1...24).step(2).each do |i|
    h1 = SunCalc.moon_position(hours_later(t, i), lat, lng)[:altitude] - hc
    h2 = SunCalc.moon_position(
      hours_later(t, i + 1), lat, lng
    )[:altitude] - hc
    a = (h0 + h2) / 2 - h1
    b = (h2 - h0) / 2
    xe = -b / (2 * a)
    ye = (a * xe + b) * xe + h1
    d = b * b - 4 * a * h1
    roots = 0
    x1 = 0
    x2 = 0
    min = i + xe if xe.abs <= 1 && ye < 0
    max = i + xe if xe.abs <= 1 && ye > 0
    if d >= 0
      dx = Math.sqrt(d) / (a.abs * 2)
      x1 = xe - dx
      x2 = xe + dx
      roots += 1 if x1.abs <= 1
      roots += 1 if x2.abs <= 1
      x1 = x2 if x1 < -1
    end
    if roots == 1
      if h0 < 0
        rise = i + x1
      else
        set = i + x1
      end
    elsif roots == 2
      rise = i + (ye < 0 ? x2 : x1)
      set = i + (ye < 0 ? x1 : x2)
    end
    break if rise && set && min && max
    h0 = h2
  end
  {}.tap do |result|
    result[:nadir] = hours_later(t, min) if min
    result[:lunar_noon] = hours_later(t, max) if max
    result[:moonrise] = hours_later(t, rise) if rise
    result[:moonset] = hours_later(t, set) if set
    result[ye > 0 ? :always_up : :always_down] = true if !rise && !set
  end
end

.sun_position(date, lat, lng) ⇒ Object

Calculates sun position for a given date, latitude, and longitude.



29
30
31
32
33
34
35
36
37
# File 'lib/sun_calc.rb', line 29

def self.sun_position(date, lat, lng)
  lw  = ONE_RADIAN * -lng
  phi = ONE_RADIAN * lat
  d   = to_days(date)
  c   = sun_coords(d)
  h   = sidereal_time(d, lw) - c[:ra]
  { azimuth: azimuth(h, phi, c[:dec]),
    altitude: altitude(h, phi, c[:dec]) }
end

.sun_times(date, lat, lng) ⇒ Object

Calculates sun times for a given date, latitude, and longitude.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/sun_calc.rb', line 40

def self.sun_times(date, lat, lng)
  lw     = ONE_RADIAN * -lng
  phi    = ONE_RADIAN * lat
  d      = to_days(date)
  n      = julian_cycle(d, lw)
  ds     = approx_transit(0, lw, n)
  m      = solar_mean_anomaly(ds)
  l      = ecliptic_longitude(m)
  dec    = declination(l, 0)
  j_noon = solar_transit_j(ds, m, l)
  { solar_noon: from_julian(j_noon),
    nadir: from_julian(j_noon - 0.5) }.tap do |result|
    @__sun_times__.each do |time|
      begin
        j_set = get_set_j(time[0] * ONE_RADIAN, lw, phi, dec, n, m, l)
        j_rise = j_noon - (j_set - j_noon)
        result[time[1].to_sym] = from_julian(j_rise)
        result[time[2].to_sym] = from_julian(j_set)
      rescue Math::DomainError
        result[time[1].to_sym] = nil
        result[time[2].to_sym] = nil
      end
    end
  end
end