File Coverage

blib/lib/Astro/MoonPhase.pm
Criterion Covered Total %
statement 96 166 57.8
branch 22 38 57.8
condition 8 18 44.4
subroutine 14 22 63.6
pod 3 19 15.7
total 143 263 54.3


line stmt bran cond sub pod time code
1             package Astro::MoonPhase;
2              
3 3     3   112192 use strict;
  3         8  
  3         140  
4 3     3   17 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  3         6  
  3         471  
5              
6             require Exporter;
7              
8             @ISA = qw(Exporter);
9             @EXPORT = qw(phase phasehunt phaselist);
10             $VERSION = '0.60';
11              
12 3         11148 use vars qw (
13             $Epoch
14             $Elonge $Elongp $Eccent $Sunsmax $Sunangsiz
15             $Mmlong $Mmlongp $Mlnode $Minc $Mecc $Mangsiz $Msmax $Mparallax $Synmonth
16             $Pi
17 3     3   17 );
  3         9  
18              
19             # Astronomical constants.
20              
21             $Epoch = 2444238.5; # 1980 January 0.0
22              
23             # Constants defining the Sun's apparent orbit.
24              
25             $Elonge = 278.833540; # ecliptic longitude of the Sun at epoch 1980.0
26             $Elongp = 282.596403; # ecliptic longitude of the Sun at perigee
27             $Eccent = 0.016718; # eccentricity of Earth's orbit
28             $Sunsmax = 1.495985e8; # semi-major axis of Earth's orbit, km
29             $Sunangsiz = 0.533128; # sun's angular size, degrees, at semi-major axis distance
30              
31             # Elements of the Moon's orbit, epoch 1980.0.
32              
33             $Mmlong = 64.975464; # moon's mean longitude at the epoch
34             $Mmlongp = 349.383063; # mean longitude of the perigee at the epoch
35             $Mlnode = 151.950429; # mean longitude of the node at the epoch
36             $Minc = 5.145396; # inclination of the Moon's orbit
37             $Mecc = 0.054900; # eccentricity of the Moon's orbit
38             $Mangsiz = 0.5181; # moon's angular size at distance a from Earth
39             $Msmax = 384401.0; # semi-major axis of Moon's orbit in km
40             $Mparallax = 0.9507; # parallax at distance a from Earth
41             $Synmonth = 29.53058868; # synodic month (new Moon to new Moon)
42              
43             # Properties of the Earth.
44              
45             $Pi = 3.14159265358979323846; # assume not near black hole nor in Tennessee
46              
47             # Handy mathematical functions.
48              
49 0 0   0 0 0 sub sgn { return (($_[0] < 0) ? -1 : ($_[0] > 0 ? 1 : 0)); } # extract sign
    0          
50 0     0 0 0 sub fixangle { return ($_[0] - 360.0 * (floor($_[0] / 360.0))); } # fix angle
51 8343     8343 0 28444 sub torad { return ($_[0] * ($Pi / 180.0)); } # deg->rad
52 0     0 0 0 sub todeg { return ($_[0] * (180.0 / $Pi)); } # rad->deg
53 7841     7841 0 15368 sub dsin { return (sin(torad($_[0]))); } # sin from deg
54 502     502 0 788 sub dcos { return (cos(torad($_[0]))); } # cos from deg
55              
56 0     0 0 0 sub tan { return sin($_[0])/cos($_[0]); }
57 0 0 0 0 0 0 sub asin { return ($_[0]<-1 or $_[0]>1) ? undef : atan2($_[0],sqrt(1-$_[0]*$_[0])); }
58             sub atan {
59 0 0   0 0 0 if ($_[0]==0) { return 0; }
  0 0       0  
60 0         0 elsif ($_[0]>0) { return atan2(sqrt(1+$_[0]*$_[0]),sqrt(1+1/($_[0]*$_[0]))); }
61 0         0 else { return -atan2(sqrt(1+$_[0]*$_[0]),sqrt(1+1/($_[0]*$_[0]))); }
62             }
63              
64             sub floor {
65 184     184 0 219 my $val = shift;
66 184         244 my $neg = $val < 0;
67 184         215 my $asint = int($val);
68 184         227 my $exact = $val == $asint;
69              
70 184 50       724 return ($exact ? $asint : $neg ? $asint - 1 : $asint);
    100          
71             }
72              
73              
74              
75             # jtime - convert internal date and time to astronomical Julian
76             # time (i.e. Julian date plus day fraction)
77              
78              
79             sub jtime {
80              
81 32     32 0 54 my $t = shift;
82 32         46 my ($julian);
83 32         205 $julian = ($t / 86400) + 2440587.5; # (seconds /(seconds per day)) + julian date of epoch
84 32         167 return ($julian);
85              
86             }
87              
88              
89             # jdaytosecs - convert Julian date to a UNIX epoch
90              
91             sub jdaytosecs {
92 441     441 0 597 my $jday = shift;
93 441         428 my $stamp;
94              
95 441         529 $stamp = ($jday - 2440587.5)*86400; # (juliandate - jdate of unix epoch)*(seconds per julian day)
96 441         1019 return($stamp);
97              
98             }
99              
100              
101              
102             # jyear - convert Julian date to year, month, day, which are
103             # returned via integer pointers to integers
104             sub jyear {
105              
106 23     23 0 60 my ($td, $yy, $mm, $dd) = @_;
107 23         45 my ($z, $f, $a, $alpha, $b, $c, $d, $e);
108              
109 23         43 $td += 0.5; # astronomical to civil
110 23         223 $z = floor($td);
111 23         43 $f = $td - $z;
112              
113 23 50       52 if ($z < 2299161.0) {
114 0         0 $a = $z;
115             } else {
116 23         55 $alpha = floor(($z - 1867216.25) / 36524.25);
117 23         70 $a = $z + 1 + $alpha - floor($alpha / 4);
118             }
119              
120              
121 23         594 $b = $a + 1524;
122 23         714 $c = floor(($b - 122.1) / 365.25);
123 23         61 $d = floor(365.25 * $c);
124 23         55 $e = floor(($b - $d) / 30.6001);
125              
126 23         56 $$dd = $b - $d - floor(30.6001 * $e) + $f;
127 23 100       57 $$mm = $e < 14 ? $e - 1 : $e - 13;
128 23 100       296 $$yy = $$mm > 2 ? $c - 4716 : $c - 4715;
129              
130             }
131              
132             ## meanphase -- Calculates time of the mean new Moon for a given
133             ## base date. This argument K to this function is the
134             ## precomputed synodic month index, given by:
135             ##
136             ## K = (year - 1900) * 12.3685
137             ##
138             ## where year is expressed as a year and fractional year.
139              
140              
141             sub meanphase {
142 59     59 0 79 my ($sdate, $k) = @_;
143 59         71 my ($t, $t2, $t3, $nt1);
144              
145             ## Time in Julian centuries from 1900 January 0.5
146 59         91 $t = ($sdate - 2415020.0) / 36525;
147 59         73 $t2 = $t * $t; ## Square for frequent use
148 59         67 $t3 = $t2 * $t; ## Cube for frequent use
149              
150 59         167 $nt1 = 2415020.75933 + $Synmonth * $k
151             + 0.0001178 * $t2
152             - 0.000000155 * $t3
153             + 0.00033 * dsin(166.56 + 132.87 * $t - 0.009173 * $t2);
154              
155 59         105 return ($nt1);
156             }
157              
158              
159             # truephase - given a K value used to determine the mean phase of the
160             # new moon, and a phase selector (0.0, 0.25, 0.5, 0.75),
161             # obtain the true, corrected phase time
162              
163             sub truephase {
164 520     520 0 1057 my ($k, $phase) = @_;
165 520         795 my ($t, $t2, $t3, $pt, $m, $mprime, $f);
166 520         695 my $apcor = 0;
167              
168 520         630 $k += $phase; # add phase to new moon time
169 520         544 $t = $k / 1236.85; # time in Julian centuries from
170             # 1900 January 0.5
171 520         939 $t2 = $t * $t; # square for frequent use
172 520         825 $t3 = $t2 * $t; # cube for frequent use
173              
174             # mean time of phase */
175 520         1689 $pt = 2415020.75933
176             + $Synmonth * $k
177             + 0.0001178 * $t2
178             - 0.000000155 * $t3
179             + 0.00033 * dsin(166.56 + 132.87 * $t - 0.009173 * $t2);
180              
181             # Sun's mean anomaly
182 520         932 $m = 359.2242
183             + 29.10535608 * $k
184             - 0.0000333 * $t2
185             - 0.00000347 * $t3;
186              
187             # Moon's mean anomaly
188 520         741 $mprime = 306.0253
189             + 385.81691806 * $k
190             + 0.0107306 * $t2
191             + 0.00001236 * $t3;
192              
193             # Moon's argument of latitude
194 520         819 $f = 21.2964
195             + 390.67050646 * $k
196             - 0.0016528 * $t2
197             - 0.00000239 * $t3;
198              
199 520 100 100     3382 if (($phase < 0.01) || (abs($phase - 0.5) < 0.01)) {
    50 66        
200             # Corrections for New and Full Moon.
201              
202 269         602 $pt += (0.1734 - 0.000393 * $t) * dsin($m)
203             + 0.0021 * dsin(2 * $m)
204             - 0.4068 * dsin($mprime)
205             + 0.0161 * dsin(2 * $mprime)
206             - 0.0004 * dsin(3 * $mprime)
207             + 0.0104 * dsin(2 * $f)
208             - 0.0051 * dsin($m + $mprime)
209             - 0.0074 * dsin($m - $mprime)
210             + 0.0004 * dsin(2 * $f + $m)
211             - 0.0004 * dsin(2 * $f - $m)
212             - 0.0006 * dsin(2 * $f + $mprime)
213             + 0.0010 * dsin(2 * $f - $mprime)
214             + 0.0005 * dsin($m + 2 * $mprime);
215 269         739 $apcor = 1;
216             }
217             elsif ((abs($phase - 0.25) < 0.01 || (abs($phase - 0.75) < 0.01))) {
218 251         743 $pt += (0.1721 - 0.0004 * $t) * dsin($m)
219             + 0.0021 * dsin(2 * $m)
220             - 0.6280 * dsin($mprime)
221             + 0.0089 * dsin(2 * $mprime)
222             - 0.0004 * dsin(3 * $mprime)
223             + 0.0079 * dsin(2 * $f)
224             - 0.0119 * dsin($m + $mprime)
225             - 0.0047 * dsin($m - $mprime)
226             + 0.0003 * dsin(2 * $f + $m)
227             - 0.0004 * dsin(2 * $f - $m)
228             - 0.0006 * dsin(2 * $f + $mprime)
229             + 0.0021 * dsin(2 * $f - $mprime)
230             + 0.0003 * dsin($m + 2 * $mprime)
231             + 0.0004 * dsin($m - 2 * $mprime)
232             - 0.0003 * dsin(2 * $m + $mprime);
233 251 100       1493 if ($phase < 0.5) {
234             # First quarter correction.
235 128         282 $pt += 0.0028 - 0.0004 * dcos($m) + 0.0003 * dcos($mprime);
236             }
237             else {
238             # Last quarter correction.
239 123         201 $pt += -0.0028 + 0.0004 * dcos($m) - 0.0003 * dcos($mprime);
240             }
241 251         373 $apcor = 1;
242             }
243 520 50       1172 if (!$apcor) {
244 0         0 die "truephase() called with invalid phase selector ($phase).\n";
245             }
246 520         1000 return ($pt);
247             }
248              
249             # phasehunt - find time of phases of the moon which surround the current
250             # date. Five phases are found, starting and ending with the
251             # new moons which bound the current lunation
252              
253             sub phasehunt {
254 14   33 14 1 31574 my $sdate = jtime(shift || time());
255 14         18 my ($adate, $k1, $k2, $nt1, $nt2);
256 0         0 my ($yy, $mm, $dd);
257              
258 14         22 $adate = $sdate - 45;
259              
260 14         50 jyear($adate, \$yy, \$mm, \$dd);
261 14         57 $k1 = floor(($yy + (($mm - 1) * (1.0 / 12.0)) - 1900) * 12.3685);
262              
263 14         30 $adate = $nt1 = meanphase($adate, $k1);
264              
265 14         20 while (1) {
266 45         53 $adate += $Synmonth;
267 45         58 $k2 = $k1 + 1;
268 45         78 $nt2 = meanphase($adate, $k2);
269 45 100 66     208 if (($nt1 <= $sdate) && ($nt2 > $sdate)) {
270 14         22 last;
271             }
272 31         107 $nt1 = $nt2;
273 31         40 $k1 = $k2;
274              
275             }
276              
277              
278              
279             return (
280 14         27 jdaytosecs(truephase($k1, 0.0)),
281             jdaytosecs(truephase($k1, 0.25)),
282             jdaytosecs(truephase($k1, 0.5)),
283             jdaytosecs(truephase($k1, 0.75)),
284             jdaytosecs(truephase($k2, 0.0))
285             );
286             }
287              
288              
289              
290             # phaselist - find time of phases of the moon between two dates
291             # times (in & out) are seconds_since_1970
292              
293             sub phaselist
294             {
295 9     9 1 308919 my ($sdate, $edate) = map { jtime($_) } @_;
  18         230  
296              
297 9         20 my (@phases, $d, $k, $yy, $mm);
298              
299 9         49 jyear($sdate, \$yy, \$mm, \$d);
300 9         57 $k = floor(($yy + (($mm - 1) * (1.0 / 12.0)) - 1900) * 12.3685) - 2;
301              
302 9         26 while (1) {
303 117         163 ++$k;
304 117         168 for my $phase (0.0, 0.25, 0.5, 0.75) {
305 450         1057 $d = truephase($k, $phase);
306              
307 450 100       1335 return @phases if $d >= $edate;
308              
309 441 100       1032 if ($d >= $sdate) {
310 371 100       700 push @phases, int(4 * $phase) unless @phases;
311 371         1106 push @phases, jdaytosecs($d);
312             } # end if date should be listed
313             } # end for each $phase
314             } # end while 1
315             } # end phaselist
316              
317              
318              
319             # kepler - solve the equation of Kepler
320              
321             sub kepler {
322 0     0 0   my ($m, $ecc) = @_;
323 0           my ($e, $delta);
324 0           my $EPSILON = 1e-6;
325              
326 0           $m = torad($m);
327 0           $e = $m;
328 0           do {
329 0           $delta = $e - $ecc * sin($e) - $m;
330 0           $e -= $delta / (1 - $ecc * cos($e));
331             } while (abs($delta) > $EPSILON);
332 0           return ($e);
333             }
334              
335              
336              
337             # phase - calculate phase of moon as a fraction:
338             #
339             # The argument is the time for which the phase is requested,
340             # expressed as a Julian date and fraction. Returns the terminator
341             # phase angle as a percentage of a full circle (i.e., 0 to 1),
342             # and stores into pointer arguments the illuminated fraction of
343             # the Moon's disc, the Moon's age in days and fraction, the
344             # distance of the Moon from the centre of the Earth, and the
345             # angular diameter subtended by the Moon as seen by an observer
346             # at the centre of the Earth.
347              
348             sub phase {
349 0   0 0 1   my $pdate = jtime(shift || time());
350              
351 0           my $pphase; # illuminated fraction
352             my $mage; # age of moon in days
353 0           my $dist; # distance in kilometres
354 0           my $angdia; # angular diameter in degrees
355 0           my $sudist; # distance to Sun
356 0           my $suangdia; # sun's angular diameter
357              
358 0           my ($Day, $N, $M, $Ec, $Lambdasun, $ml, $MM, $MN, $Ev, $Ae, $A3, $MmP,
359             $mEc, $A4, $lP, $V, $lPP, $NP, $y, $x, $Lambdamoon, $BetaM,
360             $MoonAge, $MoonPhase,
361             $MoonDist, $MoonDFrac, $MoonAng, $MoonPar,
362             $F, $SunDist, $SunAng,
363             $mpfrac);
364              
365             # Calculation of the Sun's position.
366              
367 0           $Day = $pdate - $Epoch; # date within epoch
368 0           $N = fixangle((360 / 365.2422) * $Day); # mean anomaly of the Sun
369 0           $M = fixangle($N + $Elonge - $Elongp); # convert from perigee
370             # co-ordinates to epoch 1980.0
371 0           $Ec = kepler($M, $Eccent); # solve equation of Kepler
372 0           $Ec = sqrt((1 + $Eccent) / (1 - $Eccent)) * tan($Ec / 2);
373 0           $Ec = 2 * todeg(atan($Ec)); # true anomaly
374 0           $Lambdasun = fixangle($Ec + $Elongp); # Sun's geocentric ecliptic
375             # longitude
376             # Orbital distance factor.
377 0           $F = ((1 + $Eccent * cos(torad($Ec))) / (1 - $Eccent * $Eccent));
378 0           $SunDist = $Sunsmax / $F; # distance to Sun in km
379 0           $SunAng = $F * $Sunangsiz; # Sun's angular size in degrees
380              
381              
382             # Calculation of the Moon's position.
383              
384             # Moon's mean longitude.
385 0           $ml = fixangle(13.1763966 * $Day + $Mmlong);
386              
387             # Moon's mean anomaly.
388 0           $MM = fixangle($ml - 0.1114041 * $Day - $Mmlongp);
389              
390             # Moon's ascending node mean longitude.
391 0           $MN = fixangle($Mlnode - 0.0529539 * $Day);
392              
393             # Evection.
394 0           $Ev = 1.2739 * sin(torad(2 * ($ml - $Lambdasun) - $MM));
395              
396             # Annual equation.
397 0           $Ae = 0.1858 * sin(torad($M));
398              
399             # Correction term.
400 0           $A3 = 0.37 * sin(torad($M));
401              
402             # Corrected anomaly.
403 0           $MmP = $MM + $Ev - $Ae - $A3;
404              
405             # Correction for the equation of the centre.
406 0           $mEc = 6.2886 * sin(torad($MmP));
407              
408             # Another correction term.
409 0           $A4 = 0.214 * sin(torad(2 * $MmP));
410              
411             # Corrected longitude.
412 0           $lP = $ml + $Ev + $mEc - $Ae + $A4;
413              
414             # Variation.
415 0           $V = 0.6583 * sin(torad(2 * ($lP - $Lambdasun)));
416              
417             # True longitude.
418 0           $lPP = $lP + $V;
419              
420             # Corrected longitude of the node.
421 0           $NP = $MN - 0.16 * sin(torad($M));
422              
423             # Y inclination coordinate.
424 0           $y = sin(torad($lPP - $NP)) * cos(torad($Minc));
425              
426             # X inclination coordinate.
427 0           $x = cos(torad($lPP - $NP));
428              
429             # Ecliptic longitude.
430 0           $Lambdamoon = todeg(atan2($y, $x));
431 0           $Lambdamoon += $NP;
432              
433             # Ecliptic latitude.
434 0           $BetaM = todeg(asin(sin(torad($lPP - $NP)) * sin(torad($Minc))));
435              
436             # Calculation of the phase of the Moon.
437              
438             # Age of the Moon in degrees.
439 0           $MoonAge = $lPP - $Lambdasun;
440              
441             # Phase of the Moon.
442 0           $MoonPhase = (1 - cos(torad($MoonAge))) / 2;
443              
444             # Calculate distance of moon from the centre of the Earth.
445              
446 0           $MoonDist = ($Msmax * (1 - $Mecc * $Mecc)) /
447             (1 + $Mecc * cos(torad($MmP + $mEc)));
448              
449             # Calculate Moon's angular diameter.
450              
451 0           $MoonDFrac = $MoonDist / $Msmax;
452 0           $MoonAng = $Mangsiz / $MoonDFrac;
453              
454             # Calculate Moon's parallax.
455              
456 0           $MoonPar = $Mparallax / $MoonDFrac;
457              
458 0           $pphase = $MoonPhase;
459 0           $mage = $Synmonth * (fixangle($MoonAge) / 360.0);
460 0           $dist = $MoonDist;
461 0           $angdia = $MoonAng;
462 0           $sudist = $SunDist;
463 0           $suangdia = $SunAng;
464 0           $mpfrac = fixangle($MoonAge) / 360.0;
465 0 0         return wantarray ? ( $mpfrac, $pphase, $mage, $dist, $angdia, $sudist,$suangdia ) : $mpfrac;
466             }
467              
468             1;
469             __END__