File Coverage

lib/Astro/Montenbruck/Ephemeris.pm
Criterion Covered Total %
statement 82 87 94.2
branch 11 14 78.5
condition 6 6 100.0
subroutine 22 23 95.6
pod 2 2 100.0
total 123 132 93.1


line stmt bran cond sub pod time code
1              
2             package Astro::Montenbruck::Ephemeris;
3              
4 1     1   136956 use 5.22.0;
  1         5  
5 1     1   4 use strict;
  1         2  
  1         16  
6 1     1   4 use warnings;
  1         1  
  1         22  
7 1     1   4 no warnings qw/experimental/;
  1         2  
  1         23  
8 1     1   398 use Readonly;
  1         3112  
  1         42  
9 1     1   411 use Module::Load;
  1         859  
  1         5  
10 1     1   591 use Memoize;
  1         1959  
  1         52  
11             memoize qw/_create_constructor/;
12              
13 1     1   6 use Exporter qw/import/;
  1         2  
  1         62  
14              
15             our %EXPORT_TAGS = (
16             all => [ qw/iterator find_positions/ ],
17             );
18             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} }, );
19             our $VERSION = 0.01;
20              
21 1     1   405 use Math::Trig qw/deg2rad/;
  1         10911  
  1         79  
22 1     1   8 use List::Util qw/any/;
  1         1  
  1         54  
23 1     1   444 use Astro::Montenbruck::Ephemeris::Planet qw/:ids/;
  1         3  
  1         132  
24 1     1   365 use Astro::Montenbruck::NutEqu qw/mean2true/;
  1         2  
  1         44  
25 1     1   6 use Astro::Montenbruck::MathUtils qw/diff_angle/;
  1         1  
  1         624  
26              
27             Readonly our $DAY_IN_CENT => 1 / 36525;
28              
29             # Factory function. Loads given class and returns function that wraps
30             # its constructor.
31             #
32             # Example:
33             # my $f = _create_constructor('Astro::Montenbruck::Ephemeris::Planets::Sun');
34             # my $sun = $f->(); # instantiate the object
35             # my @pos = $sun->position($t); # calculate coordinates for the moment $t
36             sub _create_constructor {
37             my $pkg = shift;
38             load $pkg;
39             sub { $pkg->new(@_) }
40             }
41              
42             # shortcut for _create_constructor
43             sub _construct {
44 24     24   471 _create_constructor(join('::', qw/Astro Montenbruck Ephemeris/, @_))
45             }
46              
47              
48             sub _iterator {
49 6     6   7 my $t = shift;
50 6         9 my $ids_ref = shift;
51 6         11 my @items = @{$ids_ref};
  6         17  
52 6         13 my %arg = @_;
53 6         13 my $sun_pos;
54             my $sun_lbr;
55 6         0 my $nut_func;
56              
57             # Return position of the Sun, that are calculated only once.
58             my $get_sun_pos = sub {
59 20 100   20   79 $sun_pos = [ _construct('Planet', $SU)->()->position($t) ]
60             unless defined $sun_pos;
61 20         103 $sun_pos
62 6         23 };
63              
64             # Return l, b, r of the Sun, l and b in radians.
65             my $get_sun_lbr = sub {
66 18     18   34 my $pos = $get_sun_pos->();
67 18 100       38 $sun_lbr = {
68             l => deg2rad($pos->[0]),
69             b => deg2rad($pos->[1]),
70             r => $pos->[2]
71             } unless defined $sun_lbr;
72 18         89 $sun_lbr;
73 6         19 };
74              
75             # function for mean2trueing mean geocentric coordinates to true
76             my $get_nut_func = sub {
77 18 100   18   38 $nut_func = mean2true($t) unless defined $nut_func;
78 18         60 $nut_func
79 6         15 };
80              
81             # Calculate required position. Sun's coordinates are calculated only once.
82             my $get_position = sub {
83 22     22   30 my $id = shift;
84 22         38 given ($id) {
85 22         47 when ($SU) {
86 2         12 return $get_sun_pos->()
87             }
88 20         108 when ($MO) {
89 2         11 return [ _construct('Planet', $id)->()->position($t) ]
90             }
91 18         85 default {
92             return [
93 18         30 _construct('Planet', $id)->()->position(
94             $t, $get_sun_lbr->(), $get_nut_func->()
95             )
96             ]
97             }
98             }
99 6         25 };
100              
101             sub {
102 28 100   28   22735 NEXT:
103             return unless @items; # no more items, stop iteration
104 24         52 my $id = shift @items;
105 24 100 100     80 goto NEXT if $id eq $PL && ($t < -1.1 || $t > 1.0);
      100        
106 22         154 [ $id, $get_position->($id) ]
107             }
108 6         22 }
109              
110              
111             sub iterator {
112 6     6 1 12579 my $t = shift;
113 6         14 my $ids_ref = shift;
114 6         16 my %arg = (with_motion => 0, @_);
115              
116 6         20 my $iter_1 = _iterator($t, $ids_ref, %arg);
117 6 50       29 return $iter_1 unless $arg{with_motion};
118              
119             # to calculate mean daily motion, we need another iterator, for the next day
120 0         0 my $iter_2 = _iterator($t + $DAY_IN_CENT, $ids_ref, %arg);
121              
122             return sub {
123 0 0   0   0 my $res = $iter_1->() or return;
124 0         0 $res->[2] = diff_angle($res->[1]->[0], $iter_2->()->[1]->[0]);
125 0         0 $res
126             }
127 0         0 }
128              
129             sub find_positions {
130 1     1 1 5323 my $t = shift;
131 1         2 my $ids_ref = shift;
132 1         2 my $callback = shift;
133              
134 1         5 my $iter = iterator($t, $ids_ref, @_);
135 1         4 while ( my $res = $iter->() ) {
136 10         136 my ($id, $pos, $motion) = @$res;
137 10         26 $callback->( $id, @$pos, $motion );
138             }
139             }
140              
141              
142             1;
143             __END__
144              
145             =pod
146              
147             =encoding UTF-8
148              
149             =head1 NAME
150              
151             Astro::Montenbruck::Ephemeris - calculate planetary positions.
152              
153             =head1 SYNOPSIS
154              
155             =head2 Iterator interface
156              
157             use Astro::Montenbruck::Ephemeris::Planet qw/@PLANETS/;
158             use Astro::Montenbruck::Ephemeris qw/iterator/;
159             use Data::Dumper;
160              
161             my $jd = 2458630.5; # Standard Julian date for May 27, 2019, 00:00 UTC.
162             my $t = ($jd - 2451545) / 36525; # Convert Julian date to centuries since epoch 2000.0
163             # for better accuracy, convert $t to Ephemeris (Dynamic) time.
164             my $iter = iterator( $t, \@PLANETS ); # get iterator function for Sun. Moon and the planets.
165              
166             while ( my $result = $iter->() ) {
167             my ($id, $co) = @$result;
168             print $id, "\n", Dumper($co), "\n"; # geocentric longitude, latitude and distance from Earth
169             }
170              
171             =head2 Callback interface
172              
173             use Astro::Montenbruck::Ephemeris::Planet qw/@PLANETS/;
174             use Astro::Montenbruck::Ephemeris qw/find_positions/;
175              
176             my $jd = 2458630.5; # Standard Julian date for May 27, 2019, 00:00 UTC.
177             my $t = ($jd - 2451545) / 36525; # Convert Julian date to centuries since epoch 2000.0
178             # for better accuracy, convert $t to Ephemeris (Dynamic) time.
179              
180             find_positions($t, \@PLANETS, sub {
181             my ($id, $lambda, $beta, $delta) = @_;
182             say "$id $lambda, $beta, $delta";
183             })
184              
185              
186             =head1 DESCRIPTION
187              
188             Calculates apparent geocentric ecliptic coordinates of the Sun, the Moon, and
189             the 8 planets. Algorithms are based on I<"Astronomy on the Personal Computer">
190             by O.Montenbruck and Th.Pfleger. The results are supposed to be precise enough
191             for amateur's purposes:
192              
193             "The errors in the fundamental routines for determining the coordinates
194             of the Sun, the Moon, and the planets amount to about 1″-3″."
195              
196             -- Introduction to the 4-th edition, p.2.
197              
198             You may use one of two interfaces: iterator and callback.
199              
200             The coordinates are referred to the I<true equinox of date> and contain corrections
201             for I<precession>, I<nutation>, I<aberration> and I<light-time>.
202              
203             =head2 Implementation details
204              
205             This module is implemented as a "factory". User may not need all the planets
206             at once, so each class is loaded lazily, by demand.
207              
208             =head2 Mean daily motion
209              
210             To calculate mean daily motion along with the celestial coordinates, use
211             C<with_motion> option:
212              
213             iterator( $t, \@PLANETS, with_motion => 1 );
214             # Or:
215             find_positions($t, \@PLANETS, $callback, with_motion => 1);
216              
217             That will slow down the program.
218              
219             =head2 Pluto
220              
221             Pluto's position is calculated only between years B<1890> and B<2100>.
222             See L<Astro::Montenbruck::Ephemeris::Planet::Pluto>.
223              
224             =head2 Universal Time vs Ephemeris Time
225              
226             For better accuracy the time must be given in I<Ephemeris Time (ET)>. To convert
227             I<UT> to I<ET>, use C<t2dt> function from L<Astro::Montenbruck::Time> module.
228              
229              
230             =head1 SUBROUTINES
231              
232             =head2 iterator($t, $ids, %options)
233              
234             Returns iterator function, which, on its turn, when called returns either C<undef>,
235             when exhausted, or arrayref, containing:
236              
237             =over
238              
239             =item *
240              
241             identifier of the celestial body, a string
242              
243             =item *
244              
245             arrayref, containing ecliptic coordinates: I<longitude> (arc-degrees),
246             I<latitude> (arc-degrees) and distance from Earth (AU).
247              
248             =item * mean daily motion, double, if C<with_motion> option is I<true>
249              
250             =back
251              
252             =head3 Positional Arguments
253              
254             =over
255              
256             =item *
257              
258             B<$t> — time in centuries since epoch 2000.0; for better precision UTC should be
259             converted to Ephemeris time, see L</Universal Time vs Ephemeris Time>.
260              
261             =item *
262              
263             B<$ids> — reference to an array of ids of celestial bodies to be calculated.
264              
265             =back
266              
267             =head3 Options
268              
269             =over
270              
271             =item *
272              
273             B<with_motion> — optional flag; when set to I<true>, there is additional B<motion>
274             field in the result; I<false> by default.
275              
276             =back
277              
278             =head2 find_positions($t, $ids, $callback, %options)
279              
280             The arguments and options are the same as for the L<iterator|/iterator($t, $ids, %options)>,
281             except the third argument, which is a B<callback function>, called on each iteration:
282              
283             $callback->($id, $lambda, $beta, $delta [, $daily_motion])
284              
285             I<$lambda>, I<$beta>, I<$delta> are ecliptic coordinates: I<longitude> (arc-degrees),
286             I<latitude> (arc-degrees) and distance from Earth (AU). The fifth argument,
287             I<$daily_motion> is defined only when C<with_motion> option is on; it is the
288             mean daily motion (arc-degrees).
289              
290              
291             =head1 AUTHOR
292              
293             Sergey Krushinsky, C<< <krushi at cpan.org> >>
294              
295             =head1 COPYRIGHT AND LICENSE
296              
297             Copyright (C) 2010-2019 by Sergey Krushinsky
298              
299             This library is free software; you can redistribute it and/or modify
300             it under the same terms as Perl itself.
301              
302             =cut