astro_moon 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/README +6 -0
  2. data/lib/astro/moon.rb +325 -0
  3. metadata +47 -0
data/README ADDED
@@ -0,0 +1,6 @@
1
+ Provides functions for lunar phases and lunar month dates (eg. new/full moon)
2
+
3
+ This file is a thoughtless manual compilation of Astro::MoonPhase perl
4
+ module (which, in turn, is influenced by moontool.c). I don't know
5
+ how it all works.
6
+
data/lib/astro/moon.rb ADDED
@@ -0,0 +1,325 @@
1
+ require 'date'
2
+
3
+ module Astro # :nodoc:
4
+ #
5
+ # Provides functions for lunar phases and lunar month dates (eg. new/full
6
+ # moon)
7
+ #
8
+ # This file is a thoughtless manual compilation of Astro::MoonPhase perl
9
+ # module (which, in turn, is influenced by moontool.c). I don't know
10
+ # how it all works.
11
+ module Moon # :doc:
12
+ class << self
13
+
14
+ # a container structure for phase() return value.
15
+ Phase = Struct.new(:phase, :illumination, :age, :distance, :angle,
16
+ :sun_distance, :sun_angle)
17
+
18
+ # a container structure for phasehunt() return value.
19
+ PhaseHunt = Struct.new(:moon_start, :first_quarter, :moon_full,
20
+ :last_quarter, :moon_end)
21
+ # Astronomical constants.
22
+ # 1980 January 0.0
23
+ EPOCH = 2444238.5
24
+
25
+ # Constants defining the Sun's apparent orbit.
26
+ #
27
+ # ecliptic longitude of the Sun at epoch 1980.0
28
+ ELONGE = 278.833540
29
+ # ecliptic longitude of the Sun at perigee
30
+ ELONGP = 282.596403
31
+ # eccentricity of Earth's orbit
32
+ ECCENT = 0.016718
33
+ # semi-major axis of Earth's orbit, km
34
+ SUNSMAX = 1.495985e8
35
+
36
+ # sun's angular size, degrees, at semi-major axis distance
37
+ SUNANGSIZ = 0.533128
38
+
39
+ # Elements of the Moon's orbit, epoch 1980.0.
40
+
41
+ # moon's mean longitude at the epoch
42
+ MMLONG = 64.975464
43
+ # mean longitude of the perigee at the epoch
44
+ MMLONGP = 349.383063
45
+ # mean longitude of the node at the epoch
46
+ MLNODE = 151.950429
47
+ # inclination of the Moon's orbit
48
+ MINC = 5.145396
49
+ # eccentricity of the Moon's orbit
50
+ MECC = 0.054900
51
+ # moon's angular size at distance a from Earth
52
+ MANGSIZ = 0.5181
53
+ # semi-major axis of Moon's orbit in km
54
+ MSMAX = 384401.0
55
+ # parallax at distance a from Earth
56
+ MPARALLAX = 0.9507
57
+ # synodic month (new Moon to new Moon)
58
+ SYNMONTH = 29.53058868
59
+
60
+
61
+ # Finds the key dates of the specified lunar month.
62
+ # Takes a DateTime object (or creates one using now() function).
63
+ # Returns a PhaseHunt struct instance.
64
+ #
65
+ # # find the date/time of the full moon in this lunar month
66
+ # Astro::Moon.phasehunt.moon_full.strftime("%D %T %Z") \
67
+ # # => "03/04/07 02:17:40 +0300"
68
+
69
+ def phasehunt(date = nil)
70
+ date = DateTime.now if !date
71
+ sdate = date.ajd
72
+
73
+ adate = sdate - 45
74
+ ad1 = DateTime.new1(adate)
75
+
76
+ k1 = ((ad1.year + ((ad1.month - 1) *
77
+ (1.0 / 12.0)) - 1900) * 12.3685).floor
78
+
79
+ adate = nt1 = meanphase(adate, k1)
80
+
81
+ while true
82
+ adate += SYNMONTH
83
+ k2 = k1 + 1
84
+ nt2 = meanphase(adate, k2)
85
+ break if nt1 <= sdate && nt2 > sdate
86
+ nt1 = nt2
87
+ k1 = k2
88
+ end
89
+
90
+ return PhaseHunt.new(*[
91
+ truephase(k1, 0.0),
92
+ truephase(k1, 0.25),
93
+ truephase(k1, 0.5),
94
+ truephase(k1, 0.75),
95
+ truephase(k2, 0.0)
96
+ ].map {
97
+ |_| _.new_offset(date.offset)
98
+ })
99
+ end
100
+
101
+ # Finds the lunar phase for the specified date.
102
+ # Takes a DateTime object (or creates one using now() function).
103
+ # Returns a Phase struct instance.
104
+ #
105
+ # # find the current moon illumination, in percents
106
+ # Astro::Moon.phase.illumination * 100 # => 63.1104513958699
107
+ #
108
+ # # find the current moon phase (new moon is 0 or 100,
109
+ # # full moon is 50, etc)
110
+ # Astro::Moon.phase.phase * 100 # => 70.7802812241989
111
+
112
+ def phase(dt = nil)
113
+ dt = DateTime.now if !dt
114
+ pdate = dt.ajd
115
+
116
+ # Calculation of the Sun's position.
117
+
118
+ day = pdate - EPOCH # date within epoch
119
+ n = ((360 / 365.2422) * day) % 360.0
120
+ m = (n + ELONGE - ELONGP) % 360.0 # convert from perigee
121
+ # co-ordinates to epoch 1980.0
122
+ ec = kepler(m, ECCENT) # solve equation of Kepler
123
+ ec = Math.sqrt((1 + ECCENT) / (1 - ECCENT)) * Math.tan(ec / 2)
124
+ ec = 2 * todeg(Math.atan(ec)) # true anomaly
125
+ lambdasun = (ec + ELONGP) % 360.0 # Sun's geocentric ecliptic
126
+ # longitude
127
+ # Orbital distance factor.
128
+ f = ((1 + ECCENT * Math.cos(torad(ec))) / (1 - ECCENT * ECCENT))
129
+ sundist = SUNSMAX / f # distance to Sun in km
130
+ sunang = f * SUNANGSIZ # Sun's angular size in degrees
131
+
132
+ # Calculation of the Moon's position.
133
+
134
+ # Moon's mean longitude.
135
+ ml = (13.1763966 * day + MMLONG) % 360.0
136
+
137
+ # Moon's mean anomaly.
138
+ mm = (ml - 0.1114041 * day - MMLONGP) % 360.0
139
+
140
+ # Moon's ascending node mean longitude.
141
+ mn = (MLNODE - 0.0529539 * day) % 360.0
142
+
143
+ # Evection.
144
+ ev = 1.2739 * Math.sin(torad(2 * (ml - lambdasun) - mm))
145
+
146
+ # Annual equation.
147
+ ae = 0.1858 * Math.sin(torad(m))
148
+
149
+ # Correction term.
150
+ a3 = 0.37 * Math.sin(torad(m))
151
+
152
+ # Corrected anomaly.
153
+ mmp = mm + ev - ae - a3
154
+
155
+ # Correction for the equation of the centre.
156
+ mec = 6.2886 * Math.sin(torad(mmp))
157
+
158
+ # Another correction term.
159
+ a4 = 0.214 * Math.sin(torad(2 * mmp))
160
+
161
+ # Corrected longitude.
162
+ lp = ml + ev + mec - ae + a4
163
+
164
+ # Variation.
165
+ v = 0.6583 * Math.sin(torad(2 * (lp - lambdasun)))
166
+
167
+ # True longitude.
168
+ lpp = lp + v
169
+
170
+ # Corrected longitude of the node.
171
+ np = mn - 0.16 * Math.sin(torad(m))
172
+
173
+ # Y inclination coordinate.
174
+ y = Math.sin(torad(lpp - np)) * Math.cos(torad(MINC))
175
+
176
+ # X inclination coordinate.
177
+ x = Math.cos(torad(lpp - np))
178
+
179
+ # Ecliptic longitude.
180
+ lambdamoon = todeg(Math.atan2(y, x))
181
+ lambdamoon += np
182
+
183
+ # Ecliptic latitude.
184
+ betam = todeg(Math.asin(Math.sin(torad(lpp - np)) *
185
+ Math.sin(torad(MINC))))
186
+
187
+ # Calculation of the phase of the Moon.
188
+
189
+ # Age of the Moon in degrees.
190
+ moonage = lpp - lambdasun
191
+
192
+ # Phase of the Moon.
193
+ moonphase = (1 - Math.cos(torad(moonage))) / 2
194
+
195
+ # Calculate distance of moon from the centre of the Earth.
196
+
197
+ moondist = (MSMAX * (1 - MECC * MECC)) /
198
+ (1 + MECC * Math.cos(torad(mmp + mec)))
199
+
200
+ # Calculate Moon's angular diameter.
201
+
202
+ moondfrac = moondist / MSMAX
203
+ moonang = MANGSIZ / moondfrac
204
+
205
+ # Calculate Moon's parallax.
206
+
207
+ moonpar = MPARALLAX / moondfrac
208
+
209
+ pphase = moonphase
210
+ mpfrac = (moonage % 360) / 360.0
211
+ mage = SYNMONTH * mpfrac
212
+ dist = moondist
213
+ angdia = moonang
214
+ sudist = sundist
215
+ suangdia = sunang
216
+ Phase.new(mpfrac, pphase, mage, dist, angdia, sudist, suangdia)
217
+ end
218
+
219
+ private
220
+
221
+ def torad(x) ; x * Math::PI / 180.0 ; end
222
+ def todeg(x) ; x * 180.0 / Math::PI ; end
223
+ def dsin(x) ; Math.sin(torad(x)) ; end
224
+ def dcos(x) ; Math.sin(torad(x)) ; end
225
+
226
+ def meanphase(sdate, k)
227
+ ## Time in Julian centuries from 1900 January 0.5
228
+ t = (sdate - 2415020.0) / 36525
229
+ t2 = t * t
230
+ t3 = t2 * t
231
+
232
+ nt1 = 2415020.75933 +
233
+ SYNMONTH * k +
234
+ 0.0001178 * t2 -
235
+ 0.000000155 * t3 +
236
+ 0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2)
237
+ end
238
+
239
+ def truephase(k, phase)
240
+ apcor = 0
241
+
242
+ k += phase # add phase to new moon time
243
+ t = k / 1236.85 # time in Julian centuries from
244
+ # 1900 January 0.5
245
+ t2 = t * t # square for frequent use
246
+ t3 = t2 * t # cube for frequent use
247
+
248
+ # mean time of phase */
249
+ pt = 2415020.75933 +
250
+ SYNMONTH * k +
251
+ 0.0001178 * t2 -
252
+ 0.000000155 * t3 +
253
+ 0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2)
254
+
255
+ # Sun's mean anomaly
256
+ m = 359.2242 + 29.10535608 * k - 0.0000333 * t2 - 0.00000347 * t3
257
+
258
+ # Moon's mean anomaly
259
+ mprime =
260
+ 306.0253 + 385.81691806 * k + 0.0107306 * t2 + 0.00001236 * t3
261
+
262
+ # Moon's argument of latitude
263
+ f = 21.2964 + 390.67050646 * k - 0.0016528 * t2 - 0.00000239 * t3
264
+
265
+ if phase < 0.01 || (phase - 0.5).abs < 0.01
266
+ # Corrections for New and Full Moon.
267
+
268
+ pt += (0.1734 - 0.000393 * t) * dsin(m) +
269
+ 0.0021 * dsin(2 * m) -
270
+ 0.4068 * dsin(mprime) +
271
+ 0.0161 * dsin(2 * mprime) -
272
+ 0.0004 * dsin(3 * mprime) +
273
+ 0.0104 * dsin(2 * f) -
274
+ 0.0051 * dsin(m + mprime) -
275
+ 0.0074 * dsin(m - mprime) +
276
+ 0.0004 * dsin(2 * f + m) -
277
+ 0.0004 * dsin(2 * f - m) -
278
+ 0.0006 * dsin(2 * f + mprime) +
279
+ 0.0010 * dsin(2 * f - mprime) +
280
+ 0.0005 * dsin(m + 2 * mprime)
281
+ apcor = 1
282
+ elsif (phase - 0.25).abs < 0.01 || (phase - 0.75).abs < 0.01
283
+ pt += (0.1721 - 0.0004 * t) * dsin(m) +
284
+ 0.0021 * dsin(2 * m) -
285
+ 0.6280 * dsin(mprime) +
286
+ 0.0089 * dsin(2 * mprime) -
287
+ 0.0004 * dsin(3 * mprime) +
288
+ 0.0079 * dsin(2 * f) -
289
+ 0.0119 * dsin(m + mprime) -
290
+ 0.0047 * dsin(m - mprime) +
291
+ 0.0003 * dsin(2 * f + m) -
292
+ 0.0004 * dsin(2 * f - m) -
293
+ 0.0006 * dsin(2 * f + mprime) +
294
+ 0.0021 * dsin(2 * f - mprime) +
295
+ 0.0003 * dsin(m + 2 * mprime) +
296
+ 0.0004 * dsin(m - 2 * mprime) -
297
+ 0.0003 * dsin(2 * m + mprime)
298
+
299
+ corr = 0.0028 - 0.0004 * dcos(m) + 0.0003 * dcos(mprime)
300
+ pt += (phase < 0.5 ? corr : -corr)
301
+ apcor = 1;
302
+ end
303
+
304
+ if !apcor || apcor == 0
305
+ raise "truephase() called with invalid phase selector (#{phase})."
306
+ end
307
+ return DateTime.new1(pt + 0.5)
308
+ end
309
+
310
+ def kepler(m, ecc)
311
+ m = torad(m)
312
+ e = m;
313
+ loop do
314
+ delta = e - ecc * Math.sin(e) - m
315
+ e -= delta / (1 - ecc * Math.cos(e))
316
+ break if delta.abs <= 1e-6
317
+ end
318
+ return e
319
+ end
320
+
321
+ end
322
+ end
323
+ end
324
+
325
+
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: astro_moon
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2007-03-10 00:00:00 +03:00
8
+ summary: A library for calculating the lunar phases and dates
9
+ require_paths:
10
+ - lib
11
+ email: dmitry.kim@gmail.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description: This library calculates to high precision the lunar phases and dates.
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - dmitry kim
31
+ files:
32
+ - lib/astro/moon.rb
33
+ - README
34
+ test_files: []
35
+
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files:
39
+ - README
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements: []
45
+
46
+ dependencies: []
47
+