File Coverage

lib/Astro/Montenbruck/Lunation.pm
Criterion Covered Total %
statement 71 99 71.7
branch 21 30 70.0
condition 18 30 60.0
subroutine 15 21 71.4
pod 2 6 33.3
total 127 186 68.2


line stmt bran cond sub pod time code
1             package Astro::Montenbruck::Lunation;
2              
3 1     1   88934 use strict;
  1         9  
  1         24  
4 1     1   5 use warnings;
  1         1  
  1         23  
5              
6 1     1   5 use Exporter qw/import/;
  1         1  
  1         39  
7 1     1   419 use Readonly;
  1         3226  
  1         45  
8 1     1   417 use Math::Trig qw/deg2rad rad2deg/;
  1         12879  
  1         69  
9 1     1   7 use POSIX qw /floor/;
  1         2  
  1         8  
10              
11 1     1   1803 use Astro::Montenbruck::Time qw/cal2jd jd2cal $J1900/;
  1         2  
  1         96  
12 1     1   6 use Astro::Montenbruck::MathUtils qw/reduce_deg diff_angle/;
  1         1  
  1         1334  
13              
14             Readonly our $NEW_MOON => 'New Moon';
15             Readonly our $FIRST_QUARTER => 'First Quarter';
16             Readonly our $FULL_MOON => 'Full Moon';
17             Readonly our $LAST_QUARTER => 'Last Quarter';
18             Readonly our $WAXING_CRESCENT => 'Waxing Crescent';
19             Readonly our $WAXING_GIBBOUS => 'Waxing Gibbous';
20             Readonly our $WANING_GIBBOUS => 'Waning Gibbous';
21             Readonly our $WANING_CRESCENT => 'Waning Crescent';
22              
23             Readonly our @PHASES =>
24             qw/$NEW_MOON $WAXING_CRESCENT $FIRST_QUARTER $WAXING_GIBBOUS
25             $FULL_MOON $WANING_GIBBOUS $LAST_QUARTER $WANING_CRESCENT/;
26              
27             my @funcs = qw/mean_phase search_event lunar_month moon_phase/;
28              
29             our %EXPORT_TAGS = (
30             phases => \@PHASES,
31             functions => \@funcs,
32             all => [ @PHASES, @funcs ]
33             );
34              
35             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
36             our $VERSION = 1.00;
37              
38             Readonly::Hash our %COEFFS => (
39             $NEW_MOON => 0.0,
40             $FIRST_QUARTER => 0.25,
41             $FULL_MOON => 0.5,
42             $LAST_QUARTER => 0.75
43             );
44              
45             sub mean_phase {
46 8     8 0 29 my ( $frac, $ye, $mo, $da ) = @_;
47 8         60 my $j1 = cal2jd( $ye, $mo, $da );
48 8         18 my $j0 = cal2jd( $ye - 1, 12, 31.5 );
49              
50 8         18 my $k1 = ( $ye - 1900 + ( ( $j1 - $j0 ) / 365 ) ) * 12.3685;
51 8         15 int( $k1 + 0.5 ) + $frac;
52             }
53              
54             # Calculates delta for Full and New Moon.
55             sub nf_delta {
56 4     4 0 8 my ( $t, $ms, $mm, $tms, $tmm, $tf ) = @_;
57              
58 4         26 ( 1.734e-1 - 3.93e-4 * $t ) * sin($ms)
59             + 2.1e-3 * sin($tms)
60             - 4.068e-1 * sin($mm)
61             + 1.61e-2 * sin($tmm)
62             - 4e-4 * sin( $mm + $tmm )
63             + 1.04e-2 * sin($tf)
64             - 5.1e-3 * sin( $ms + $mm )
65             - 7.4e-3 * sin( $ms - $mm )
66             + 4e-4 * sin( $tf + $ms )
67             - 4e-4 * sin( $tf - $ms )
68             - 6e-4 * sin( $tf + $mm )
69             + 1e-3 * sin( $tf - $mm )
70             + 5e-4 * sin( $ms + $tmm );
71             }
72              
73             # Calculates delta for First ans Last quarters .
74             sub fl_delta {
75 4     4 0 7 my ( $t, $ms, $mm, $tms, $tmm, $tf ) = @_;
76              
77 4         27 ( 0.1721 - 0.0004 * $t ) * sin($ms)
78             + 0.0021 * sin($tms)
79             - 0.6280 * sin($mm)
80             + 0.0089 * sin($tmm)
81             - 0.0004 * sin( $tmm + $mm )
82             + 0.0079 * sin($tf)
83             - 0.0119 * sin( $ms + $mm )
84             - 0.0047 * sin( $ms - $mm )
85             + 0.0003 * sin( $tf + $ms )
86             - 0.0004 * sin( $tf - $ms )
87             - 0.0006 * sin( $tf + $mm )
88             + 0.0021 * sin( $tf - $mm )
89             + 0.0003 * sin( $ms + $tmm )
90             + 0.0004 * sin( $ms - $tmm )
91             - 0.0003 * sin( $tms + $mm );
92             }
93              
94             sub search_event {
95 8     8 1 6431 my ( $date, $quarter ) = @_;
96 8         12 my ( $ye, $mo, $da ) = @$date;
97              
98 8         25 my $k = mean_phase( $COEFFS{$quarter}, @$date );
99              
100 8         16 my $t1 = $k / 1236.85;
101 8         11 my $t2 = $t1 * $t1;
102 8         11 my $t3 = $t2 * $t1;
103              
104 8         24 my $c = deg2rad( 166.56 + ( 132.87 - 9.173e-3 * $t1 ) * $t1 );
105              
106             # time of the mean phase
107 8         88 my $j
108             = 0.75933 + 29.53058868 * $k
109             + 0.0001178 * $t2
110             - 1.55e-07 * $t3
111             + 3.3e-4 * sin($c);
112              
113             my $assemble = sub {
114 24     24   44 deg2rad(
115             reduce_deg( $_[0] + $_[1] * $k + $_[2] * $t2 + $_[3] * $t3 ) );
116 8         28 };
117              
118 8         17 my $ms = $assemble->( 359.2242, 29.105356080, -0.0000333, -0.00000347 );
119 8         48 my $mm = $assemble->( 306.0253, 385.81691806, 0.0107306, 0.00001236 );
120 8         44 my $f = $assemble->( 21.2964, 390.67050646, -0.0016528, -0.00000239 );
121 8         41 my $delta = do {
122 8         10 my $tms = $ms + $ms;
123 8         10 my $tmm = $mm + $mm;
124 8         9 my $tf = $f + $f;
125 8 100 100     23 if ( $quarter eq $NEW_MOON || $quarter eq $FULL_MOON ) {
126 4         34 nf_delta( $t1, $ms, $mm, $tms, $tmm, $tf );
127             }
128             else {
129 4         44 my $w = 0.0028 - 0.0004 * cos($ms) + 0.0003 * cos($ms);
130 4 100       9 $w = -$w if $quarter eq $LAST_QUARTER;
131 4         20 fl_delta( $t1, $ms, $mm, $tms, $tmm, $tf ) + $w;
132             }
133             };
134 8         10 $j += $delta + $J1900;
135 8 50       20 wantarray() ? ($j, rad2deg($f))
136             : $j
137              
138             }
139              
140             sub _find_quarter {
141 0     0   0 my ( $q, $y, $m, $d ) = @_;
142 0         0 my $j = search_event( [ $y, $m, floor($d) ], $q );
143 0         0 { type => $q, jd => $j };
144             }
145              
146             sub _find_newmoon {
147 0     0   0 my $ye = shift;
148 0         0 my $mo = shift;
149 0         0 my $da = shift;
150 0     0   0 my %arg = ( find_next => sub { }, step => 28, @_ );
151              
152             # find New Moon closest to the date
153 0         0 my $data = _find_quarter( $NEW_MOON, $ye, $mo, $da );
154 0 0       0 if ( $arg{find_next}->( $data->{jd} ) ) {
155 0         0 my ( $y, $m, $d ) = jd2cal( $data->{jd} + $arg{step} );
156 0         0 return _find_newmoon( $y, $m, $d, %arg );
157             }
158 0         0 $data;
159             }
160              
161             sub lunar_month {
162 0     0 1 0 my $jd = shift;
163 0         0 my ( $ye, $mo, $da ) = jd2cal($jd);
164             my $head = _find_newmoon(
165             $ye, $mo, $da,
166 0     0   0 find_next => sub { $_[0] > $jd },
167 0         0 step => -28
168             );
169             my $tail = _find_newmoon(
170             $ye, $mo, $da,
171 0     0   0 find_next => sub { $_[0] < $jd },
172 0         0 step => 28
173             );
174 0         0 my ( $y, $m, $d ) = jd2cal $head->{jd};
175 0         0 my @trunc = map { _find_quarter( $_, $y, $m, $d ) }
  0         0  
176             ( $FIRST_QUARTER, $FULL_MOON, $LAST_QUARTER );
177              
178 0         0 my $pre;
179             map {
180 0         0 my $cur = $_;
  0         0  
181 0         0 $cur->{current} = 0;
182 0 0       0 if ( defined $pre ) {
183 0 0 0     0 $pre->{current} = $jd >= $pre->{jd} && $jd < $cur->{jd} ? 1 : 0;
184             }
185 0         0 $pre = $cur;
186             } ( $head, @trunc, $tail );
187             }
188              
189             sub moon_phase {
190 15     15 0 13630 my %arg = (sun => undef, moon => undef, @_);
191 15         47 my $d = reduce_deg(diff_angle($arg{sun}, $arg{moon})); # age in degrees
192 15         20 my $days = $d / 12.1907;
193             my $get_phase = sub {
194 15 100 66 15   58 return $NEW_MOON if $d >= 0 && $d < 45;
195 13 100 66     35 return $WAXING_CRESCENT if $d >= 45 && $d < 90;
196 11 100 66     33 return $FIRST_QUARTER if $d >= 90 && $d < 135;
197 10 100 66     29 return $WAXING_GIBBOUS if $d >= 135 && $d < 180;
198 8 100 66     27 return $FULL_MOON if $d >= 180 && $d < 225;
199 6 100 66     20 return $WANING_GIBBOUS if $d >= 225 && $d < 270;
200 4 100 66     14 return $LAST_QUARTER if $d >= 270 && $d < 315;
201 2 50 33     12 return $WANING_CRESCENT if $d >= 315 && $d < 360;
202 15         60 };
203 15         27 my $phase = $get_phase->();
204 15 50       134 return wantarray() ? ($phase, $d, $days) : $phase
205             }
206              
207              
208              
209             1;
210             __END__
211              
212              
213             =pod
214              
215             =encoding UTF-8
216              
217             =head1 NAME
218              
219             Astro::Montenbruck::Lunation - Lunar quarters.
220              
221             =head1 SYNOPSIS
222              
223             use Astro::Montenbruck::Lunation qw/:all/;
224              
225             # find instant of New Moon closest to 2019 Aug, 12
226             $jd = search_event([2019, 8, 12], $NEW_MOON);
227             # returns 2458696.63397517
228              
229             # find, which lunar phase corresponds to Moon longitude of 9.926
230             # and Sun longitude of 316.527
231             $phase = lunar_phase(moon => 9.926, sun => 316.527);
232             # returns 'Waxing Crescent'
233              
234             =head1 DESCRIPTION
235              
236             Searches lunar quarters. Algorithms are based on
237             I<"Astronomy with your PC"> by I<Peter Duffett-Smith>, I<Second Edition>, I<Cambridge University Press}, 1990>.
238              
239              
240             =head1 EXPORT
241              
242             =head2 CONSTANTS
243              
244             =head3 PHASES
245              
246             =over
247              
248             =item * C<$NEW_MOON>
249              
250             =item * C<$WAXING_CRESCENT>
251              
252             =item * C<$FIRST_QUARTER>
253              
254             =item * C<$WAXING_GIBBOUS>
255              
256             =item * C<$FULL_MOON>
257              
258             =item * C<$WANING_GIBBOUS>
259              
260             =item * C<$LAST_QUARTER>
261              
262             =item * C<$WANING_CRESCENT>
263              
264             =back
265              
266              
267              
268             =head1 SUBROUTINES
269              
270             =head2 search_event(date => $arr, quarter => $scalar)
271              
272             Calculate instant of apparent lunar phase closest to the given date.
273              
274             =head3 Named Arguments
275              
276             =over
277              
278             =item * B<date> — array of B<year> (astronomical, zero-based), B<month> [1..12]
279             and B<day>, [1..31].
280              
281             =item * B<quarter> — which quarter, one of: C<$NEW_MOON>, C<$FIRST_QUARTER>,
282             C<$FULL_MOON> or C<$LAST_QUARTER>.
283              
284             =back
285              
286             =head3 Returns
287              
288             In scalar context returns I<Standard Julian day> of the event, dynamic time.
289              
290             In list context:
291              
292             =over
293              
294             =item * I<Standard Julian day> of the event, dynamic time.
295              
296             =item * Argument of latitude, arc-degrees. This value is required for detecting elipses.
297              
298             =back
299              
300             =head2 lunar_month($jd)
301              
302             Find lunar quarters around the given date
303              
304             =head3 Arguments
305              
306             =over
307              
308             =item * B<jd> — Standard Julian date
309              
310             =head3 Returns
311              
312             Array of 5 hashes, each hash representing a successive lunar quarter. Their order is always the same:
313              
314             =over
315              
316             =item 1.
317              
318             B<New Moon>
319              
320             =item 2.
321              
322             B<First Quarter>
323              
324             =item 3.
325              
326             B<Full Moon>
327              
328             =item 4.
329              
330             B<Last Quarter>
331              
332             =back
333              
334             =item 4.
335              
336             B<The next New Moon>
337              
338             =back
339              
340              
341             Each hash contains 3 elements:
342              
343             =over
344              
345             =item * B<type>
346              
347             One of the constants representing the main Quarter: C<$NEW_MOON>, C<$FIRST_QUARTER>, C<$FULL_MOON>, C<$LAST_QUARTER>.
348              
349             =item * B<jd>
350              
351             Standard Julian Date of the event,
352              
353             =item * B<current>
354              
355             I<True> if the the given date lies within the quarter.
356              
357             =back
358              
359             =head4 Example
360              
361             lunar_month(2459614.5) gives:
362              
363             (
364             {
365             type => 'New Moon',
366             jd => 2459611.74248269, # time when the quarter starts
367             current => 1 # since 2459611.74248269 < 2459614.5 < 2459619.07819525, our date belongs to New Moon phase.
368             },
369             {
370             type => 'First Quarter',
371             current => 0,
372             jd => 2459619.07819525
373             },
374             {
375             type => 'Full Moon',
376             current => 0,
377             jd => 2459627.20811964
378             },
379             {
380             current => 0,
381             jd => 2459634.44073709'
382             type => 'Last Quarter'
383             },
384             {
385             current => 0,
386             type => 'New Moon',
387             jd => 2459641.23491532
388             }
389             );
390              
391              
392             =head2 lunar_phase(sun => $decimal, moon => $decimal)
393              
394             Given Sun and Moon longitudes, detects a lunar phase.
395              
396             =head3 Named Arguments
397              
398             =over
399              
400             =item * B<sun> — longitude of the Sun, in arc-degrees
401              
402             =item * B<moon> — longitude of the Moon, in arc-degrees
403             =back
404              
405             =head3 Returns
406              
407             In scalar context the phase name, one of the L<PHASES>.
408              
409             In list context:
410              
411             =over
412              
413             =item * name of the phase.
414              
415             =item * Moon age in arc-degrees
416              
417             =item * Moon age in days
418              
419             =back
420              
421              
422             =head1 AUTHOR
423              
424             Sergey Krushinsky, C<< <krushi at cpan.org> >>
425              
426             =head1 COPYRIGHT AND LICENSE
427              
428             Copyright (C) 2009-2022 by Sergey Krushinsky
429              
430             This library is free software; you can redistribute it and/or modify
431             it under the same terms as Perl itself.
432              
433             =cut