File Coverage

lib/Astro/Montenbruck/RiseSet.pm
Criterion Covered Total %
statement 84 127 66.1
branch 4 14 28.5
condition n/a
subroutine 22 29 75.8
pod 3 3 100.0
total 113 173 65.3


line stmt bran cond sub pod time code
1             package Astro::Montenbruck::RiseSet;
2              
3 3     3   2491 use strict;
  3         30  
  3         81  
4 3     3   13 use warnings;
  3         5  
  3         84  
5 3     3   12 no warnings qw/experimental/;
  3         7  
  3         106  
6 3     3   14 use feature qw/switch/;
  3         5  
  3         282  
7              
8 3     3   17 use Exporter qw/import/;
  3         5  
  3         90  
9 3     3   20 use Readonly;
  3         21  
  3         128  
10 3     3   1641 use Memoize;
  3         6567  
  3         193  
11             memoize qw/_get_obliquity/;
12              
13 3     3   22 use Math::Trig qw/:pi deg2rad/;
  3         6  
  3         368  
14 3     3   17 use Astro::Montenbruck::MathUtils qw/frac/;
  3         6  
  3         103  
15 3     3   1439 use Astro::Montenbruck::Time qw/jd_cent/;
  3         7  
  3         237  
16 3     3   1374 use Astro::Montenbruck::CoCo qw/ecl2equ/;
  3         6  
  3         165  
17 3     3   1175 use Astro::Montenbruck::NutEqu qw/obliquity/;
  3         6  
  3         148  
18 3     3   1158 use Astro::Montenbruck::Ephemeris qw/iterator/;
  3         9  
  3         154  
19 3     3   18 use Astro::Montenbruck::Ephemeris::Planet qw/:ids/;
  3         6  
  3         327  
20             use Astro::Montenbruck::RiseSet::Constants
21 3     3   922 qw/:altitudes :twilight :events :states/;
  3         7  
  3         542  
22 3     3   1298 use Astro::Montenbruck::RiseSet::Plarise qw/rst_func/;
  3         8  
  3         160  
23 3     3   1308 use Astro::Montenbruck::RiseSet::Sunset qw/riseset_func/;
  3         7  
  3         3688  
24              
25             our %EXPORT_TAGS = ( all => [qw/rst riseset twilight/], );
26             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
27             our $VERSION = 0.02;
28              
29             Readonly::Array our @TWILIGHT_TYPES =>
30             ( $TWILIGHT_ASTRO, $TWILIGHT_NAUTICAL, $TWILIGHT_CIVIL );
31              
32             sub _get_obliquity { obliquity( $_[0] ) }
33              
34             sub _get_equatorial {
35 674     674   3625 my ( $id, $jd ) = @_;
36 674         4251 my $t = jd_cent($jd);
37 674         2552 my $iter = iterator( $t, [$id] );
38 674         1861 my $res = $iter->();
39 674         10590 my @ecl = @{ $res->[1] }[ 0 .. 1 ];
  674         1865  
40 674         20685 map { deg2rad($_) } ecl2equ( @ecl, _get_obliquity($t) );
  1348         8281  
41             }
42              
43             sub twilight {
44             my %arg = (
45             type => $TWILIGHT_NAUTICAL,
46       0     on_event => sub { },
47       0     on_noevent => sub { },
48             @_
49 11     11 1 9464 );
50 11         120 my $type = delete $arg{type};
51 11 50       58 die "Unknown twilight type: \"$type\"" unless exists $H0_TWL{$type};
52 11         121 my $on_event = delete $arg{on_event};
53 11         21 my $on_noevent = delete $arg{on_noevent};
54              
55 11 50       30 if (wantarray) {
56 0         0 my %res;
57             riseset_func(%arg)->(
58             on_event => sub {
59 0     0   0 my ( $evt, $jd ) = @_;
60 0         0 $res{$evt} = $jd;
61 0         0 $on_event->(@_);
62             },
63             on_noevent => sub {
64 0     0   0 $on_noevent->(@_);
65             },
66             %arg,
67 0     0   0 get_position => sub { _get_equatorial( $SU, $_[0] ) },
68 0         0 sin_h0 => sin( deg2rad( $H0_TWL{$type} ) ),
69             );
70 0         0 return %res;
71             }
72              
73             riseset_func(%arg)->(
74             %arg,
75 247     247   441 get_position => sub { _get_equatorial( $SU, $_[0] ) },
76 11         68 sin_h0 => sin( deg2rad( $H0_TWL{$type} ) ),
77             on_event => $on_event,
78             on_noevent => $on_noevent
79             );
80             }
81              
82             sub riseset {
83 0     0 1 0 my %arg = @_;
84 0         0 my $pla = delete $arg{planet};
85 0         0 my $h0 = do {
86 0         0 given ($pla) {
87 0         0 $H0_SUN when $SU;
88 0         0 $H0_MOO when $MO;
89 0         0 default { $H0_PLA }
  0         0  
90             }
91             };
92 0         0 my $func = riseset_func(%arg);
93             sub {
94 0     0   0 my %arg = ( on_event => sub { }, on_noevent => sub { }, @_ );
95              
96 0 0       0 if (wantarray) {
97              
98             # if caller asks for a result, collect events to %res hash
99 0         0 my %res;
100             $func->(
101 0         0 get_position => sub { _get_equatorial( $pla, $_[0] ) },
102             sin_h0 => sin( deg2rad($h0) ),
103             on_event => sub {
104 0         0 my ( $evt, $jd ) = @_;
105 0         0 $arg{on_event}->(@_);
106 0         0 $res{$evt} = { ok => 1, jd => $jd };
107             },
108             on_noevent => sub {
109 0         0 my ($state) = shift;
110 0         0 $arg{on_noevent}->(@_);
111 0 0       0 my $evt =
112             $state eq $STATE_NEVER_RISES ? $EVT_RISE : $EVT_SET;
113 0         0 $res{$evt} = { ok => 0, state => $state };
114             }
115 0         0 );
116 0         0 return %res;
117             }
118              
119             # if caller doesn't ask for a result, just call the function with the callbacks
120             $func->(
121 0         0 get_position => sub { _get_equatorial( $pla, $_[0] ) },
122             sin_h0 => sin( deg2rad($h0) ),
123             on_event => $arg{on_event},
124             on_noevent => $arg{on_noevent},
125 0         0 );
126             }
127 0         0 }
128              
129             sub rst {
130              
131             # build top-level function for any event and any celestial object
132             # for given time and place
133 1     1 1 1384 my $rst = rst_func(@_);
134              
135             sub {
136 9     9   3153 my $obj = shift;
137 9         56 my %arg = ( on_event => sub { }, on_noevent => sub { }, @_ );
138              
139 9         23 my $h0 = do {
140 9         17 given ($obj) {
141 9         27 $H0_SUN when $SU;
142 8         49 $H0_MOO when $MO;
143 8         34 default { $H0_PLA }
  8         17  
144             }
145             };
146              
147             # build second level functon for calculating any event for given object
148             my $evt_func = $rst->(
149 18         228 get_position => sub { _get_equatorial( $obj, $_[0] ) },
150 9         72 sin_h0 => sin( deg2rad($h0) )
151             );
152              
153 9 50       27 if (wantarray) {
154              
155             # if caller asks for a result, collect events to %res hash
156 0         0 my %res;
157 0         0 for (@RS_EVENTS) {
158 0         0 my ( $state, $jd ) = $evt_func->($_);
159 0 0       0 if ( $state eq $_ ) {
160 0         0 $arg{on_event}->( $_, $jd );
161 0         0 $res{$_} = { ok => 1, jd => $jd };
162             }
163             else {
164 0         0 $arg{on_noevent}->( $_, $state );
165 0         0 $res{$_} = { ok => 0, state => $state };
166             }
167             }
168 0         0 return %res;
169             }
170              
171             # if caller doesn't ask for a result, just call the function with the callbacks
172 9         40 for (@RS_EVENTS) {
173 27         7431 my ( $state, $jd ) = $evt_func->($_);
174 27 50       48 if ( $state eq $_ ) {
175 27         74 $arg{on_event}->( $_, $jd );
176             }
177             else {
178 0           $arg{on_noevent}->( $_, $state );
179             }
180             }
181             }
182 1         6 }
183              
184             1;
185             __END__
186              
187             =pod
188              
189             =encoding UTF-8
190              
191             =head1 NAME
192              
193             Astro::Montenbruck::RiseSet - rise, set, transit.
194              
195             =head1 SYNOPSIS
196              
197             use Astro::Montenbruck::Ephemeris::Planet qw/:ids/;
198             use Astro::Montenbruck::MathUtils qw/frac/;
199             use Astro::Montenbruck::RiseSet::Constants qw/:all/;
200             use Astro::Montenbruck::RiseSet qw/:all/;
201              
202             # create function for calculating rise/set/transit events for Munich, Germany, on March 23, 1989.
203             my $func = rst(
204             date => [1989, 3, 23],
205             phi => 48.1,
206             lambda => -11.6
207             );
208              
209             # calculate Moon rise, set and transit
210             $func->(
211             $MO,
212             on_event => sub {
213             my ($evt, $jd) = @_;
214             say "$evt: $jd";
215             },
216             on_noevent => sub {
217             my ($evt, $state) = @_; # $STATE_CIRCUMPOLAR or $STATE_NEVER_RISES
218             say "$evt: $state";
219             }
220             );
221              
222             # alternatively, call the function in list context:
223             my %res = $func->($MO); # result structure is described below
224              
225             # calculate civil twilight
226             twilight(
227             date => [1989, 3, 23],
228             phi => 48.1,
229             lambda => -11.6,
230             on_event => sub {
231             my ($evt, $jd) = @_;
232             say "$evt: $jd";
233             },
234             on_noevent => sub {
235             my $state = shift;
236             say $state;
237             }
238             );
239              
240              
241             =head1 VERSION
242              
243             Version 0.02
244              
245             =head1 DESCRIPTION
246              
247             High level interface for calculating rise, set and transit times of celestial
248             bodies, as well as twilight of different types.
249              
250             There are two low-level functions for calculating the events, based on based on different algorithms:
251              
252             =over
253              
254             =item 1.
255              
256             L<Astro::Montenbruck::RiseSet::Plarise::rst>, which calculates rise, set and transit times using I<iterative method>.
257              
258             =item 2.
259              
260             L<Astro::Montenbruck::RiseSet::Sunset::riseset_func>, which calculates rise and set times using I<quadratic interpolation>.
261              
262             =back
263              
264             Both of them are described in I<"Astronomy On The Personal Computer"> by O.Montenbruck and T.Phleger.
265             However, they are built on different algorithms: B<riseset_func> utilizes quadratic interpolation
266             while B<rst> is iterative. Along with rise and set, B<rst> gives transit times. At the other hand,
267             B<riseset_func> is a base for calculating twilight.
268              
269             To take into account I<parallax>, I<refraction> and I<apparent radius> of the
270             bodies, we use average corrections to geometric altitudes:
271              
272             =over
273              
274             =item * sunrise, sunset : B<-0 deg 50 min>
275              
276             =item * moonrise, moonset : B<0 deg 8 min>
277              
278             =item * stars and planets : B<-0 deg 34 min>
279              
280             =back
281              
282             =head2 TWILIGHT
283              
284             The library also calculates the times of the beginning of the morning twilight
285             (I<dawn>) and end of the evening twilight (I<dusk>).
286              
287             Twilight occurs when Earth's upper atmosphere scatters and reflects sunlight
288             which illuminates the lower atmosphere. Astronomers define the three stages of
289             twilight – I<civil>, I<nautical>, and I<astronomical> – on the basis of the Sun's
290             elevation which is the angle that the geometric center of the Sun makes with the
291             horizon.
292              
293             =over
294              
295             =item * I<astronomical>
296              
297             Sun altitude is B<-18 deg> In the morning, the sky is completely dark before the
298             onset of astronomical twilight, and in the evening, the sky becomes completely
299             dark at the end of astronomical twilight. Any celestial bodies that can be
300             viewed by the naked eye can be observed in the sky after the end of this phase.
301              
302             =item * I<nautical>
303              
304             Sun altitude is B<-12 deg>. This twilight period is less bright than civil twilight
305             and artificial light is generally required for outdoor activities.
306              
307             =item * I<civil>
308              
309             Sun altitude is B<-6 deg>. Civil twilight is the brightest form of twilight.
310             There is enough natural sunlight during this period that artificial light may
311             not be required to carry out outdoor activities. Only the brightest celestial
312             objects can be observed by the naked eye during this time.
313              
314             =back
315              
316             =head1 CAVEATS
317              
318             Sometimes rise and set happen on different calendar dates. For example, here is the output of C<riseset.pl>
319             script:
320              
321             $ perl .\script\riseset.pl --date=1989-03-28 --place=48.1 -11.6 --timezone=UTC
322              
323             Date : 1989-03-28 UTC
324             Place : 48N06, 011E35
325             Time Zone : UTC
326              
327             rise transit set
328             Moon 23:34:17 03:23:59 07:10:54
329              
330             This directly depends on time zone. Since event time is always given as Julian date,
331             it is not hard to determine correct order of events.
332              
333             =head1 EXPORT
334              
335             =head2 FUNCTIONS
336              
337             =over
338              
339             =item * L</rst( %args )>
340              
341             =item * L</riseset( %args )>
342              
343             =item * L</twilight( %args )>
344              
345             =back
346              
347             =head1 FUNCTIONS
348              
349              
350             =head2 rst( %args )
351              
352             Returns function for calculating times of rises, sets and transits of celestial bodies. See
353             L<Astro::Montenbruck::RiseSet::Plarise/rst> .
354              
355             =head3 Named Arguments
356              
357             =over
358              
359             =item *
360              
361             B<date> - array of B<year> (astronomical, zero-based), B<month> [1..12] and B<day>, [1..31].
362              
363             =item *
364              
365             B<phi> - geographical latitude, degrees, positive northward
366              
367             =item *
368              
369             B<lambda> - geographical longitude, degrees, positive westward
370              
371             =back
372              
373             =head3 Returns
374              
375             function, which calculates rise, set and transit for a celestial body.
376             It accepts celestial body identifier as positional argument (see L<Astro::Montenbruck::Ephemeris::Planet>)
377             and two optional callbacks as named arguments:
378              
379             =over
380              
381             =item *
382              
383             B<on_event($event, $jd> - callback called when the event time is determined. The first argument
384             is one of: C<$EVT_RISE>, C<$EVT_SET> or C<$EVT_TRANSIT> constants (see L<Astro::Montenbruck::RiseSet::Constants>),
385             the second - I<Standard Julian Date>.
386              
387             =item *
388              
389             B<on_noevent($event, $state> - callback called when the body is I<circumpolar> or I<never rises>.
390             The first argument is then one of: C<$EVT_RISE>, C<$EVT_SET> or C<$EVT_TRANSIT>, the second - either
391             C<$STATE_CIRCUMPOLAR> or C<$STATE_NEVER_RISES>.
392              
393             =back
394              
395              
396             =head4 List context
397              
398             When called in list context:
399            
400             my %res = func();
401              
402             the function returns a hash:
403            
404             (
405             rise => $hashref,
406             set => $hashref,
407             transit => $hashref
408             )
409            
410             When rise or set takes place, C<$hashref> contains:
411              
412             {ok => 1, jd => JD}
413              
414             JD is a Standard Julian Date. Otherwise,
415            
416             {ok => 0, state => STATE}
417              
418             STATE is C<$STATE_CIRCUMPOLAR> or C<$STATE_NEVER_RISES>.
419              
420              
421             =head2 riseset( %args )
422              
423             Returns function for calculating times of rises and sets of given celestial body. See
424             L<Astro::Montenbruck::RiseSet::Sunset/riseset_func>.
425              
426             =head3 Named Arguments
427              
428             =over
429              
430             =item *
431              
432             B<planet> - celestial body identifier (see L<Astro::Montenbruck::Ephemeris::Planet>)
433              
434             =item *
435              
436             B<date> - array of B<year> (astronomical, zero-based), B<month> [1..12] and B<day>, [1..31].
437              
438             =item *
439              
440             B<phi> - geographical latitude, degrees, positive northward
441              
442             =item *
443              
444             B<lambda> - geographical longitude, degrees, positive westward
445              
446             =back
447              
448             =head3 Returns
449              
450             function, which calculates rise and set times of the planet. It accepts and two callbacks as named arguments:
451              
452             =over
453              
454             =item *
455              
456             B<on_event($event, $jd>)
457             callback called when the event time is determined. The first argument
458             is one of: C<$EVT_RISE>, C<$EVT_SET> or C<$EVT_TRANSIT> constants (see L<Astro::Montenbruck::RiseSet::Constants>),
459             the second - I<Standard Julian Date>.
460              
461             =item *
462              
463             B<on_noevent($state>
464             callback called when the body is I<circumpolar> or I<never rises>.
465             The argument is either C<$STATE_CIRCUMPOLAR> or C<$STATE_NEVER_RISES>.
466              
467             =back
468              
469             When called in list context, returns a hash, described in L<rst( %args )/List context>, except
470             that C<transit> key is missing.
471              
472             =head2 twilight( %args )
473              
474             Function for calculating twilight. See L</TWILIGHT EVENT FUNCTION> below.
475              
476             =head3 Named Arguments
477              
478             =over
479              
480             =item *
481              
482             B<type> - type of twilight, C<$TWILIGHT_NAUTICAL>, C<$TWILIGHT_ASTRO>
483             or C<$TWILIGHT_CIVIL>, see L<Astro::Montenbruck::RiseSet::Constants/TYPES OF TWILIGHT>.
484              
485             =item *
486              
487             B<date> - array of B<year> (astronomical, zero-based), B<month> [1..12] and B<day>, [1..31].
488              
489             =item *
490              
491             B<phi> - geographical latitude, degrees, positive northward
492              
493             =item *
494              
495             B<lambda> - geographical longitude, degrees, positive westward
496              
497             =item *
498              
499             B<on_event> - callback called when the event time is determined. The arguments are:
500              
501             =over
502              
503             =item *
504              
505             Event type, one of C<$EVT_RISE> or C<$EVT_SET>, L<Astro::Montenbruck::RiseSet::Constants/EVENTS>.
506             The first indicates I<dawn>, the second - I<dusk>.
507              
508             =item *
509              
510             time of the event, I<Standard Julian date>.
511              
512             =back
513              
514             =item *
515              
516             B<on_noevent> is called when the event never happens, either because the body never rises,
517             or is circumpolar. The argument is respectively C<$STATE_NEVER_RISES> or C<$STATE_CIRCUMPOLAR>,
518             see L<Astro::Montenbruck::RiseSet::Constants/STATES>.
519              
520             =back
521              
522              
523             =head1 AUTHOR
524              
525             Sergey Krushinsky, C<< <krushi at cpan.org> >>
526              
527             =head1 COPYRIGHT AND LICENSE
528              
529             Copyright (C) 2010-2022 by Sergey Krushinsky
530              
531             This library is free software; you can redistribute it and/or modify
532             it under the same terms as Perl itself.
533              
534             =cut