File Coverage

lib/Astro/Montenbruck/RiseSet/Sunset.pm
Criterion Covered Total %
statement 73 79 92.4
branch 5 8 62.5
condition 8 9 88.8
subroutine 16 16 100.0
pod 1 1 100.0
total 103 113 91.1


line stmt bran cond sub pod time code
1             package Astro::Montenbruck::RiseSet::Sunset;
2              
3 3     3   17 use strict;
  3         7  
  3         81  
4 3     3   15 use warnings;
  3         5  
  3         79  
5 3     3   13 no warnings qw/experimental/;
  3         5  
  3         88  
6 3     3   22 use feature qw/switch/;
  3         6  
  3         189  
7              
8 3     3   22 use Exporter qw/import/;
  3         6  
  3         90  
9 3     3   14 use Readonly;
  3         5  
  3         128  
10              
11 3     3   16 use Math::Trig qw/:pi deg2rad/;
  3         24  
  3         365  
12              
13 3     3   21 use Astro::Montenbruck::MathUtils qw/quad/;
  3         14  
  3         128  
14 3     3   16 use Astro::Montenbruck::Time qw/cal2jd jd_cent/;
  3         5  
  3         136  
15 3     3   18 use Astro::Montenbruck::Time::Sidereal qw/ramc/;
  3         7  
  3         121  
16 3     3   15 use Astro::Montenbruck::RiseSet::Constants qw/:events :states/;
  3         8  
  3         2388  
17              
18             our @EXPORT_OK = qw/riseset_func/;
19             our $VERSION = 0.02;
20              
21             sub _cs_phi {
22 22     22   35 my $phi = shift;
23 22         69 my $rphi = deg2rad($phi);
24 22         268 cos($rphi), sin($rphi);
25             }
26              
27             # Calculates sine of the altitude at hourly intervals.
28             sub _sin_alt {
29 654     654   1523 my ( $jd, $lambda, $cphi, $sphi, $get_position ) = @_;
30 654         1706 my ( $ra, $de ) = $get_position->($jd);
31              
32             # hour angle
33 654         16039 my $tau = deg2rad( ramc( $jd, $lambda ) ) - $ra;
34 654         6017 $sphi * sin($de) + $cphi * cos($de) * cos($tau);
35             }
36              
37             sub riseset_func {
38 22     22 1 7848 my %arg = ( date => undef, phi => undef, lambda => undef, @_ );
39 22         45 my $jd0 = cal2jd( @{ $arg{date} } );
  22         126  
40 22         107 my ( $cphi, $sphi ) = _cs_phi( $arg{phi} );
41              
42             my $sin_alt = sub {
43 654     654   1417 my ( $hour, $get_position ) = @_;
44 654         2305 _sin_alt( $jd0 + $hour / 24, $arg{lambda}, $cphi, $sphi,
45             $get_position );
46 22         81 };
47              
48             sub {
49             # h0 = altitude corection
50             my %arg = (
51             sin_h0 => undef, # sine of altitude correction
52             get_position =>
53             undef
54             , # function for calculation equatorial coordinates of the body
55             on_event => sub { }, # callback for rise/set event
56             on_noevent => sub { }, # callback when an event is missing
57             @_
58 32     32   699 );
59              
60 32         109 my $get_coords = $arg{get_position};
61 32         69 my $sin_h0 = $arg{sin_h0};
62 32         50 my $on_event = $arg{on_event};
63              
64 32         47 my $hour = 1;
65 32         81 my $y_minus = $sin_alt->( $hour - 1, $get_coords ) - $sin_h0;
66 32         94 my $above = $y_minus > 0;
67 32         81 my ( $rise_found, $set_found ) = ( 0, 0 );
68              
69 32         141 my $jd_with_hour = sub { $jd0 + $_[0] / 24 };
  62         296  
70              
71             # loop over search intervals from [0h-2h] to [22h-24h]
72 32   100     59 do {
      100        
73 311         685 my $y_0 = $sin_alt->( $hour, $get_coords ) - $sin_h0;
74 311         852 my $y_plus = $sin_alt->( $hour + 1, $get_coords ) - $sin_h0;
75              
76             # find parabola through three values $y_minus, $y_0, $y_plus
77 311         995 my ( $nz, $xe, $ye, @zeroes ) = quad( $y_minus, $y_0, $y_plus );
78 311         583 given ($nz) {
79 311         729 when (1) {
80 62 100       169 if ( $y_minus < 0 ) {
81 31         116 $on_event->(
82             $EVT_RISE, $jd_with_hour->( $hour + $zeroes[0] )
83             );
84 31         19967 $rise_found = 1;
85             }
86             else {
87 31         112 $on_event->(
88             $EVT_SET, $jd_with_hour->( $hour + $zeroes[0] )
89             );
90 31         21994 $set_found = 1;
91             }
92             }
93 249         480 when (2) {
94 0 0       0 if ( $ye < 0 ) {
95 0         0 $on_event->(
96             $EVT_RISE, $jd_with_hour->( $hour + $zeroes[1] )
97             );
98 0         0 $on_event->(
99             $EVT_SET, $jd_with_hour->( $hour + $zeroes[0] )
100             );
101             }
102             else {
103 0         0 $on_event->(
104             $EVT_RISE, $jd_with_hour->( $hour + $zeroes[0] )
105             );
106 0         0 $on_event->(
107             $EVT_SET, $jd_with_hour->( $hour + $zeroes[1] )
108             );
109             }
110 0         0 ( $rise_found, $set_found ) = ( 1, 1 );
111             }
112             }
113              
114             # prepare for next interval
115 311         475 $y_minus = $y_plus;
116 311         1849 $hour += 2;
117             } until ( ( $hour >= 36 ) || ( $rise_found && $set_found ) );
118              
119 32 50 66     678 $arg{on_noevent}->( $above ? $STATE_CIRCUMPOLAR : $STATE_NEVER_RISES )
    100          
120             unless ( $rise_found || $set_found );
121             }
122 22         121 }
123              
124             1;
125             __END__
126              
127             =pod
128              
129             =encoding UTF-8
130              
131             =head1 NAME
132              
133             Astro::Montenbruck::RiseSet::Sunset — rise and set.
134              
135             =head1 SYNOPSIS
136              
137             use Astro::Montenbruck::MathUtils qw/frac/;
138             use Astro::Montenbruck::RiseSet::Constants qw/:events :altitudes/;
139             use Astro::Montenbruck::RiseSet::Sunset qw/:riseset_func/;
140              
141             my $func = riseset_func(
142             date => [1989, 3, 23],
143             phi => 48.1,
144             lambda => -11.6
145             );
146              
147             $func->(
148             get_position => sub {
149             my $jd = shift;
150             # return equatorial coordinates of the celestial body for the Julian Day.
151             },
152             sin_h0 => sin( deg2rad($H0_PLANET) ),
153             on_event => sub {
154             my ($evt, $jd) = @_;
155             say "$evt: $jd";
156             },
157             on_noevent => sub {
158             my $state = shift;
159             say $state;
160             }
161             );
162              
163             =head1 VERSION
164              
165             Version 0.01
166              
167             =head1 DESCRIPTION
168              
169             Low level routines for calculating rise and set times of celestial bodies.
170              
171             =head1 FUNCTIONS
172              
173             =head2 riseset_func ( %args )
174              
175             time of rise and set events.
176              
177             =head3 Named Arguments
178              
179             =over
180              
181             =item * B<get_position> — function, which given I<Standard Julian Day>,
182             returns equatorial coordinates of the celestial body, in radians.
183              
184             =item * B<date> — array of B<year> (astronomical, zero-based), B<month> [1..12]
185             and B<day>, [1..31].
186              
187              
188             =item * B<phi> — geographic latitude, degrees, positive northward
189              
190             =item * B<lambda> —geographic longitude, degrees, positive westward
191              
192             =item * B<get_position> — function, which given I<Standard Julian Day>,
193             returns equatorial coordinates of the celestial body, in radians.
194              
195             =item * B<sin_h0> — sine of the I<standard altitude>, i.e. the geometric altitude
196             of the center of the body at the time of apparent rising or setting.
197              
198              
199             =item * C<on_event> callback is called when the event time is determined.
200             The arguments are:
201              
202             =over
203              
204             =item * event type, one of C<$EVT_RISE> or C<$EVT_SET>
205              
206             =item * Universal time of the event
207              
208             =back
209              
210             on_event => sub { my ($evt, $ut) = @_; ... }
211              
212             =item * C<on_noevent> is called when the event does not happen at the given date,
213             either because the body never rises, or is circumpolar. The argument is respectively
214             C<$STATE_NEVER_RISES> or C<$STATE_CIRCUMPOLAR>.
215              
216             on_noevent => sub { my $state = shift; ... }
217              
218             =back
219              
220             =head1 AUTHOR
221              
222             Sergey Krushinsky, C<< <krushi at cpan.org> >>
223              
224             =head1 COPYRIGHT AND LICENSE
225              
226             Copyright (C) 2010-2022 by Sergey Krushinsky
227              
228             This library is free software; you can redistribute it and/or modify
229             it under the same terms as Perl itself.
230              
231             =cut