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 4     4   169133 use 5.22.0;
  4         27  
5 4     4   22 use strict;
  4         8  
  4         77  
6 4     4   19 use warnings;
  4         8  
  4         104  
7 4     4   19 no warnings qw/experimental/;
  4         8  
  4         112  
8 4     4   528 use Readonly;
  4         3915  
  4         183  
9 4     4   2029 use Module::Load;
  4         4518  
  4         24  
10 4     4   883 use Memoize;
  4         2397  
  4         220  
11             memoize qw/_create_constructor/;
12              
13 4     4   25 use Exporter qw/import/;
  4         8  
  4         364  
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 4     4   526 use Math::Trig qw/deg2rad/;
  4         13303  
  4         296  
22 4     4   26 use List::Util qw/any/;
  4         8  
  4         233  
23 4     4   820 use Astro::Montenbruck::Ephemeris::Planet qw/:ids/;
  4         9  
  4         654  
24 4     4   567 use Astro::Montenbruck::NutEqu qw/mean2true/;
  4         9  
  4         174  
25 4     4   25 use Astro::Montenbruck::MathUtils qw/diff_angle/;
  4         63  
  4         3061  
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 694     694   6898 _create_constructor(join('::', qw/Astro Montenbruck Ephemeris/, @_))
45             }
46              
47              
48             sub _iterator {
49 649     649   1094 my $t = shift;
50 649         1021 my $ids_ref = shift;
51 649         978 my @items = @{$ids_ref};
  649         1565  
52 649         1440 my %arg = @_;
53 649         1441 my $sun_pos;
54             my $sun_lbr;
55 649         0 my $nut_func;
56              
57             # Return position of the Sun, that are calculated only once.
58             my $get_sun_pos = sub {
59 475 100   475   1345 $sun_pos = [ _construct('Planet', $SU)->()->position($t) ]
60             unless defined $sun_pos;
61 475         10461 $sun_pos
62 649         2539 };
63              
64             # Return l, b, r of the Sun, l and b in radians.
65             my $get_sun_lbr = sub {
66 45     45   96 my $pos = $get_sun_pos->();
67 45 100       158 $sun_lbr = {
68             l => deg2rad($pos->[0]),
69             b => deg2rad($pos->[1]),
70             r => $pos->[2]
71             } unless defined $sun_lbr;
72 45         509 $sun_lbr;
73 649         1910 };
74              
75             # function for mean2trueing mean geocentric coordinates to true
76             my $get_nut_func = sub {
77 45 100   45   152 $nut_func = mean2true($t) unless defined $nut_func;
78 45         212 $nut_func
79 649         1523 };
80              
81             # Calculate required position. Sun's coordinates are calculated only once.
82             my $get_position = sub {
83 665     665   1047 my $id = shift;
84 665         1079 given ($id) {
85 665         1490 when ($SU) {
86 430         2605 return $get_sun_pos->()
87             }
88 235         1533 when ($MO) {
89 190         1058 return [ _construct('Planet', $id)->()->position($t) ]
90             }
91 45         225 default {
92             return [
93 45         111 _construct('Planet', $id)->()->position(
94             $t, $get_sun_lbr->(), $get_nut_func->()
95             )
96             ]
97             }
98             }
99 649         1757 };
100              
101             sub {
102 671 100   671   29696 NEXT:
103             return unless @items; # no more items, stop iteration
104 667         1282 my $id = shift @items;
105 667 100 100     2078 goto NEXT if $id eq $PL && ($t < -1.1 || $t > 1.0);
      100        
106 665         4285 [ $id, $get_position->($id) ]
107             }
108 649         2381 }
109              
110              
111             sub iterator {
112 649     649 1 16764 my $t = shift;
113 649         1038 my $ids_ref = shift;
114 649         1950 my %arg = (with_motion => 0, @_);
115              
116 649         2054 my $iter_1 = _iterator($t, $ids_ref, %arg);
117 649 50       2945 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 6580 my $t = shift;
131 1         3 my $ids_ref = shift;
132 1         3 my $callback = shift;
133              
134 1         5 my $iter = iterator($t, $ids_ref, @_);
135 1         4 while ( my $res = $iter->() ) {
136 10         164 my ($id, $pos, $motion) = @$res;
137 10         33 $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